编程辑略 sosohu Blog: sosohu.github.io

Size: px
Start display at page:

Download "编程辑略 sosohu Blog: sosohu.github.io"

Transcription

1 编程辑略 sosohu Blog: sosohu.github.io

2 前言 背景 说到程序员面试中的算法题面试, 我们不得不去做很多准备, 最简单粗暴的做法就是去刷题, 本书也就是作者在知名刷题网站 leetcode 刷题过程中针对各个算法题的解法总结以及一些基本知识的补充, 由于作者水平有限, 著作此书仅为交流, 不具备任何权威性, 有什么不正确的地方敬请指教. 目标读者和假设 本书假定读者熟悉基本的数据结构, 比如熟悉数组, 链表, 树, 图, 堆, 栈, 队列和图等概念并且对各种数据结构的特性有着深刻的认识. 本书假定读者熟悉算法导论上的一些常用和经典算法, 比如各种排序算法, 树和图的经典算法, 字符串匹配, 动态规划, 贪心, 回溯, 分治等常见算法策略. 由于本书的代码都是由 C++ 编写, 所以可能还需要读者能够大致了解 C++ 的一些语法和库以免读代码阅读带来困扰. 本书的内容 本书的主要内容分为两部分, 第一部分也就是前八章是从数据结构的视角来看到问题, 后面的九章则是从算法的角度来看待问题 本书的代码 本书的代码都是采用 C++ 编写, 遵守 C++ 11 标准, 所有代码都是通过 -O3 -std=c++0x 编译选项使用 g++ 编译运行过. 对于所有的出现在 leetcode 上的问题的解答代码都是在 leetcode 上 accept 过, 完整解答源码可以访问作者的 github 查看 ; 而其他代码则不能保证一定是没有问题的. 本书的编写 本书采用 latex 编写, 编写的模板是作者自己定义的, 所以比较简陋, 希望能够多加包涵. 关于作者 本书作者是 sosohu, 相关联系方式如下, 欢迎与我联系 [email protected] Blog: Github: 1

3 2 致谢 感谢 leetcode 网站提供了如此便捷的刷题机会. 感谢 soulmachine 的 leetcode 题解, 这是一份非常好的题解, 作者水平也很高. 感谢同实验室的一起刷题的 zhuoyuan 同学,CarlSama 同学,cjneo 同学和 pmon 同学.

4 目录 第 1 章 : 链表 链表的基本操作 InsertNode DeleteNode RGetKthNode ReverseList 链表的基本问题 GetMidNode IsMeetList Intersection of Two Linked Lists IsCircleList GetFirstCircleNode MergeSortedList SortList Insertion Sort List 基于链表的问题 Add Two Numbers Remove Nth Node From End of List Merge k Sorted Lists Swap Nodes in Pairs Reverse Nodes in k-group Rotate List Reorder List Remove Linked List Elements Remove Duplicates from Sorted List Remove Duplicates from Sorted List II Partition List Reverse Linked List II Copy List with Random Pointer 第 2 章 : 树 二叉树的遍历 PreOrederTraversal InOrederTraversal PostOrederTraversal LevelOrderTraversal Recover Binary Search Tree 二叉树的建立 Construct Binary Tree from Preorder and Inorder Traversal Construct Binary Tree from Postorder and Inorder Traversal Convert Sorted Array to Binary Search Tree Convert Sorted List to Binary Search Tree Unique Binary Search Trees

5 目录 Unique Binary Search Trees II 二叉树的属性 Validate Binary Search Tree Symmetric Tree Same Tree Maximum Depth of Binary Tree Minimum Depth of Binary Tree Path Sum Path Sum II Binary Tree Maximum Path Sum Sum Root to Leaf Numbers Least Common Ancest 其他 Flatten Binary Tree to Linked List Populating Next Right Pointers in Each Node Populating Next Right Pointers in Each Node II Binary Search Tree Iterator 第 3 章 : 字符串 库函数 strlen strcat strcmp strcpy strncpy memcpy 字符串经典问题 strstr atoi Reverse Words in a String Valid Palindrome LongestPalindrome Longest Substring Without Repeating Characters Anagrams Valid Number Regular Expression Matching Wildcard Matching Edit Distance Minimum Window Substring 字符串其他问题 Integer to Roman Roman to Integer Longest Common Prefix Count and Say Add Binary ZigZag Conversion Length of Last Word Text Justification Compare Version Numbers 第 4 章 : 数组 变长数组 经典问题 Remove Duplicates from Sorted Array

6 目录 Remove Duplicates from Sorted Array II Remove Element First Missing Positive Rotate Image Rotate Array 相关问题 Spiral Matrix Spiral Matrix II Set Matrix Zeroes Pascal s Triangle Pascal s Triangle II 第 5 章 : 栈和队列 基本概念 栈的设计与实现 相关问题 Valid Parentheses Trapping Rain Water Largest Rectangle in Histogram Evaluate Reverse Polish Notation Min Stack 第 6 章 : 图 基本概念 图的邻接矩阵表示 图的邻接表示 经典问题 相关问题 Clone Graph Course Schedule Course Schedule II 第 7 章 : 哈希 基本概念 Hash 表的设计与实现 相关问题 Substring with Concatenation of All Words Valid Sudoku Isomorphic Strings Contains Duplicate Two Sum Sum Sum Longest Consecutive Sequence 第 8 章 : 其他数据结构 堆 Implement Heap Algorithm 字典树 Implement Trie 其他 LRU Cache 第 9 章 : 排序 基本概念

7 目录 经典排序算法 Insert Sort Bubble Sort Selection Sort Merge Sort Heap Sort Quick Sort Count Sort Bucket Sort 基于排序的问题 Merge Intervals Insert Interval Sort Colors Maximum Gap Largest Number 第 10 章 : 二分查找 基本概念 经典问题 相关问题 Median of Two Sorted Arrays Sum Closest Search in Rotated Sorted Array Search in Rotated Sorted Array II Search for a Range Search Insert Position Divide Two Integers Pow(x, n) Sqrt(x) Search a 2D Matrix Find Minimum in Rotated Sorted Array Find Minimum in Rotated Sorted Array II Find Peak Element 第 11 章 : 分治 基本概念 经典问题 相关问题 第 12 章 : 搜索 基本概念 宽度优先搜索 Word Ladder Word Ladder II Number of Islands Surrounded Regions Binary Tree Right Side View 深度优先问题 A* 搜索 第 13 章 : 回溯 基本概念 经典问题 N-Queens N-Queens II

8 目录 相关问题 Letter Combinations of a Phone Number Generate Parentheses Sudoku Solver Combination Sum Combination Sum II Permutations Permutations II Permutation Sequence Combinations Subsets Subsets II Word Search Gray Code Restore IP Addresses Palindrome Partitioning 第 14 章 : 贪心 基本概念 经典问题 相关问题 Jump Game Jump Game II Gas Station Candy Best Time to Buy and Sell Stock I Best Time to Buy and Sell Stock II Container With Most Water 第 15 章 : 动态规划 基本概念 经典问题 Maximum Subarray Maximal Rectangle Triangle House Robber 相关问题 Longest Valid Parentheses Unique Paths Unique Paths II Minimum Path Sum Climbing Stairs Scramble String Decode Ways Interleaving String Distinct Subsequences Best Time to Buy and Sell Stock III Best Time to Buy and Sell Stock IV Palindrome Partitioning II Word Break Word Break II Maximum Product Subarray Dungeon Game

9 目录 8 第 16 章 : 位操作 基本概念 经典问题 相关问题 Single Number Single Number II Majority Element Reverse Bits Number of 1 Bits Bitwise AND of Numbers Range Repeated DNA Sequences 第 17 章 : 数学 基本概念 经典问题 Gcd Lcm Prime Number Next Permutation 相关问题 Reverse Integer Palindrome Number Multiply Strings Plus One Max Points on a Line Fraction to Recurring Decimal Excel Sheet Column Title Excel Sheet Column Number Factorial Trailing Zeroes Happy Number Count Primes

10 第 1 章链表 1.1 链表的基本操作 链表的基本操作大致包括插入, 删除, 反转等, 这些基本操作是链表问题最核心的部分, 很多链表问题都需要这三种操作的某种或者某几种 InsertNode Given a list-node pointer, insert a new node after the node that the pointer to. Care : 时间复杂度 O(1), 空间复杂度 O(1) 1 void insertnode(listnode *ppos, ListNode *pinsert){ 2 if(!ppos!pinsert) return; 3 pinsert->next = ppos->next; 4 ppos->next = pinsert; 5 } Given a list-node pointer, insert a new node before the node that the pointer to. Trick : 时间复杂度 O(1), 空间复杂度 O(1) 这里给出节点指针, 要求插在该节点之前, 我们可以 先插到该节点之后再交换两节点的值, 即完成插入. 1 void insertnode(listnode *ppos, ListNode *pinsert){ 2 if(!ppos!pinsert) return; 3 pinsert->next = ppos->next; 4 ppos->next = pinsert; 5 int tmp = ppos->val; 6 ppos->val = pinsert->val; 7 pinsert->val = tmp; 8 } DeleteNode Given a list-node pointer, delete the node after the node that the pointer to. Care : 时间复杂度 O(1), 空间复杂度 O(1) 1 void deletenode(listnode *pdelete){ 2 if(!pdelete!pdelete->next) return; 3 ListNode* cur = pdelete->next; 4 pdelete->next = pdelete->next->next; 5 delete cur; 6 } 1

11 1.1 链表的基本操作 2 Given a list-node pointer, delete the node that the pointer to. Trick : 时间复杂度 O(1), 空间复杂度 O(1) 这里给出节点指针, 要求删除该节点之前, 我们可以 先将后序节点的值拷过来, 再删除后序节点, 当然, 如果当前节点是尾节点则必须从链表头部开始遍历然后删除了. 1 void deletenode(listnode *phead, ListNode *pdelete){ 2 if(!phead!pdelete) return; 3 if(pdelete->next){ 4 // 不是最后一个节点, 只需要复制下一个节点的值 5 // 过来然后删除下一个节点, O(1) 6 ListNode *tmp = pdelete->next; 7 pdelete->val = pdelete->next->val; 8 pdelete->next = pdelete->next->next; 9 delete tmp; // 很容易忘记 10 }else{ 11 // 是最后一个节点, 只能从前遍历到倒数第二个节点 12 if(phead == pdelete){ 13 delete phead; 14 phead = NULL; 15 } 16 ListNode *pos = phead; 17 while(pos->next!= pdelete){ 18 pos = pos->next; 19 } 20 pos->next = NULL; 21 delete pdelete; // 这一句很容易忘记 22 } 23 } 虽然, 当节点是尾节点首需要 O(n) 次操作, 但是平均下来时间复杂度是 O(1) RGetKthNode Given a list, return the last Kth Node. 迭代 : 时间复杂度 O(n), 空间复杂度 O(1) 两个指针问题. 1 ListNode * RGetKthNode(ListNode *phead, unsigned int k){ 2 if(k < 1) return NULL; 3 ListNode *pre = phead; 4 ListNode *last = phead; 5 while(k-- > 0 && last){ 6 last = last->next; 7 } 8 if(!last) return NULL; 9 while(last){ 10 last = last->next; 11 pre = pre->next; 12 } 13 return pre; 14 } ReverseList Reverse a singly linked list. (leetcode 206)

12 1.1 链表的基本操作 3 头插法 : 时间复杂度 O(n), 空间复杂度 O(1) 新建一个头节点, 然后依次将后面节点插入到头节点之后, 便形成链表的倒序. 1 ListNode *ReverseList(ListNode *phead){ 2 if(!phead!phead->next) return phead; 3 ListNode * newhead = new ListNode(0); 4 ListNode *pos = phead, *tmp; 5 newhead->next = phead; 6 while(pos->next){ 7 tmp = pos->next; 8 pos->next = tmp->next; 9 tmp->next = newhead->next; 10 newhead->next = tmp; 11 } 12 return newhead->next; 13 } 就地反转 : 时间复杂度 O(n), 空间复杂度 O(1) 所谓就地反转就是每次把 i 指向 i+1 直接转为 i 指向 i+1 即可. 1 ListNode *ReverseList(ListNode *phead){ 2 if(!phead!phead->next) return phead; 3 ListNode *pre = phead, *cur = phead->next, *next = NULL; 4 phead->next = NULL; 5 while(cur){ 6 next = cur->next; 7 cur->next = pre; 8 pre = cur; 9 cur = next; 10 } 11 return pre; 12 } 递归 : 时间复杂度 O(n), 空间复杂度 O(1) 链表其实就是一个二叉树, 所以可以套用二叉树的递归做法. 1 ListNode *ReverseList(ListNode *phead){ 2 if(!phead!phead->next) return phead; 3 // 返回为段倒转后的首节点 leftleft 4 ListNode *left = ReverseList(pHead->next); 5 // phead->next 一开始是段的首节点, 倒转后就是末节点 left 6 phead->next->next = phead; 7 phead->next = NULL; 8 return left; 9 }

13 1.2 链表的基本问题 链表的基本问题 链表的基本问题大多都是很经典的链表问题, 例如中间节点, 是否有环等等 GetMidNode Given a list, return the middle node. 快慢指针 : 时间复杂度 O(n), 空间复杂度 O(1) 慢指针以每步一个节点的速度前进, 快指针以每步两个节点的速度前进 1 ListNode* getmidnode(listnode *phead){ 2 if(!phead!phead->next) return phead; 3 ListNode *slow = phead, *fast = phead->next; 4 while(fast && fast->next){ 5 slow = slow->next; 6 fast = fast->next; 7 fast = fast->next; 8 } 9 return slow; 10 } IsMeetList Given two lists, return wheather the two lists meet. Null : 时间复杂度 (n+m), 空间复杂度 O(1) 判断两个链表是否相交, 只需要判断两个链表最后一个节点是否为同一个即可 1 bool ismeetlist(listnode *phead1, ListNode *phead2){ 2 if(!phead1!phead2) return false; 3 ListNode *pos1 = phead1, *pos2 = phead2; 4 while(pos1->next){ 5 pos1 = pos1->next; 6 } 7 while(pos2->next){ 8 pos2 = pos2->next; 9 } 10 return pos1 == pos2; 11 } Intersection of Two Linked Lists Write a program to find the node at which the intersection of two singly linked lists begins. leetcode 160 Note: Write a program to find the node at which the intersection of two singly linked lists begins. If the two linked lists have no intersection at all, return null. The linked lists must retain their original structure after the function returns. You may assume there are no cycles anywhere in the entire linked structure. Your code should preferably run in O(n) time and use only O(1) memory.

14 1.2 链表的基本问题 5 双指针 : 时间复杂度 (n+m), 空间复杂度 O(1) 找到两个链表第一个相交点, 首先遍历两个链表, 得到两个链表的长度, 比如说 l1, l2. 然后先移动较长的那个链表 ( 比如 l1 l2), 移动 l1-l2 步, 这样双方剩余的节点数就相等了. 接着以前往后走, 第一次相遇的点就是答案 1 ListNode * getintersectionnode( ListNode * heada, ListNode * headb) { 2 if(!heada!headb) return NULL; 3 int la = 1, lb = 1; 4 ListNode *posa = heada, *posb = headb; 5 while(posa->next posb->next){ 6 if(posa->next){ 7 la ++; 8 posa = posa->next; 9 } 10 if(posb->next){ 11 lb ++; 12 posb = posb->next; 13 } 14 } 15 if(posa!= posb) return NULL; 16 if(lb > la){ 17 swap(heada, headb); 18 swap(la, lb); 19 } 20 for(auto i = 0; i < la -lb; i++) 21 heada = heada->next; 22 while(heada!= headb){ 23 heada = heada->next; 24 headb = headb->next; 25 } 26 return heada; 27 } IsCircleList Given a linked list, determine if it has a cycle in it.(leetcode 141) 快慢指针 : 时间复杂度 O(n), 空间复杂度 O(1) 快慢指针, 慢指针每步一个节点, 快指针每步两个节点, 如果有环肯定会相遇 1 bool IsCircleList(ListNode *phead){ 2 if(!phead!phead->next) return false; 3 ListNode *slow = phead, *fast = phead; 4 while(fast && fast->next){ 5 slow = slow->next; 6 fast = fast->next->next; 7 if(slow == fast) return true; 8 } 9 return false; 10 } GetFirstCircleNode Given a linked list, return the node where the cycle begins. If there is no cycle, return null. (leetcode 142)

15 1.2 链表的基本问题 6 快慢指针 : 时间复杂度 O(n), 空间复杂度 O(1) 1 ListNode * GetFirstCircleNode( ListNode* phead){ 2 if(!phead!phead->next) return phead; 3 ListNode *slow = phead, *fast = phead; 4 // 指针先进入环内 fast 5 while(fast && fast->next){ 6 slow = slow->next; 7 fast = fast->next->next; 8 if(slow == fast) 9 break; 10 } 11 if(!fast!fast->next) 12 return NULL; // 不存在环 13 // 指针再次从头开始 slow, 指针减速 fast 14 slow = phead; 15 while(slow!= fast){ 16 slow = slow->next; 17 fast = fast->next; 18 } 19 return slow; // 相遇点就是环的入口 20 } MergeSortedList Merge two sorted lists.(leetcode 21) 迭代 : 时间复杂度 (n), 空间复杂度 O(1) 1 ListNode* MergeSortedList(ListNode *phead1, ListNode *phead2){ 2 if(!phead1) return phead2; 3 if(!phead2) return phead1; 4 ListNode *pos1 = phead1, *pos2 = phead2; 5 ListNode *ret = phead1->val < phead2->val? (pos1 = pos1->next, phead1) 6 : (pos2 = pos2->next, phead2); 7 ListNode *pos = ret; 8 while(pos1 && pos2){ 9 if(pos1->val < pos2->val){ 10 pos->next = pos1; 11 pos1 = pos1->next; 12 }else{ 13 pos->next = pos2; 14 pos2 = pos2->next; 15 } 16 pos = pos->next; 17 } 18 if(pos1){ 19 pos->next = pos1; 20 } 21 if(pos2){ 22 pos->next = pos2; 23 } 24 return ret; 25 }

16 1.2 链表的基本问题 7 递归 : 时间复杂度 (n), 空间复杂度 O(1) 1 ListNode* MergeSortedList(ListNode *phead1, ListNode *phead2){ 2 if(!phead1) return phead2; 3 if(!phead2) return phead1; 4 ListNode *ret, *pos1 = phead1, *pos2 = phead2; 5 ret = phead1->val < phead2->val? (pos1 = pos1->next, phead1) : ( pos2 = pos2->next, phead2); 6 ret->next = MergeSortedList(pos1, pos2); 7 return ret; 8 } SortList Sort a linked list in O(n log n) time using constant space complexity. (leetcode 148) 迭代 : 时间复杂度 O(nlgn), 空间复杂度 O(1) 仿照 SGI STL 里面的 List 容器的 sort 函数, 实现迭代版的归并排序 1 ListNode *sortlist(listnode *phead){ 2 if(!phead!phead->next) return phead; 3 vector<listnode*> counter; 4 for(int i = 0; i < 64; i++) 5 counter.push_back(null); 6 ListNode * carry; 7 ListNode *pos = phead; 8 int fill = 0; 9 while(pos){ 10 carry = new ListNode(pos->val); 11 pos = pos->next; 12 int i = 0; 13 for(i = 0; i < fill && counter[i]; i++){ 14 carry = MergeSortedList(carry, counter[i]); 15 counter[i] = NULL; 16 } 17 counter[i] = carry; 18 if(i == fill) fill++; 19 } 20 for(int i = 1; i < fill; i++){ 21 counter[i] = MergeSortedList(counter[i-1], counter[i]); 22 } 23 return counter[ fill-1]; 24 } 这个迭代版的归并方法很有用, 几乎所有的归并函数都可以这样改装 递归 : 时间复杂度 O(nlgn), 空间复杂度 O(lgn) 1 ListNode *ListSort(ListNode *phead){ 2 if(!phead!phead->next) return phead; 3 ListNode *mid = getmidnode(phead); 4 ListNode *right = phead, *left = mid->next; 5 mid->next = NULL; 6 right = ListSort(right); 7 left = ListSort(left); 8 phead = MergeSortedList(right, left); 9 return phead; 10 }

17 1.2 链表的基本问题 Insertion Sort List Sort a linked list using insertion sort. (leetcode 147) 排序 : 时间复杂度 O(n 2 ), 空间复杂度 O(1) 和数组的插入排序几乎一样, 使用一个新的头节点更简单. 1 ListNode * insertionsortlist( ListNode * head) { 2 if(!head!head->next) return head; 3 ListNode newhead(0); 4 newhead.next = head; 5 ListNode *pos = head, *begin = &newhead, *tmp; 6 while(pos->next){ 7 begin = &newhead; 8 while(begin!= pos && begin->next->val <= pos->next->val){ 9 begin = begin->next; 10 } 11 if(begin == pos){ 12 pos = pos->next; 13 }else{ 14 tmp = pos->next; 15 pos->next = tmp->next; 16 tmp->next = begin->next; 17 begin->next = tmp; 18 } 19 } 20 return newhead. next; 21 }

18 1.3 基于链表的问题 基于链表的问题 基于链表的问题, 大多需要前面所说的基本操作和基本问题的组合, 另外, 对于链表这个容器的认识也要很深刻才行, 链表容器的优势在于能够快速的插入和删除指定节点, 而且节省空间, 但是它的问题就是不能随机访问, 可以说和 vector 是互补的 Add Two Numbers You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. (leetcode 2) 举例 : Input: (2 4 3) + (5 6 4), Output: Care : 时间复杂度 O(n+m), 空间复杂度 O(1) 这道题主要是要注意进位和一些边界条件判断的简化, 这里有很多边界条件, 怎么把很多 if 语句归纳到一起使代码更简洁. 1 ListNode *addtwonumbers(listnode *l1, ListNode *l2) { 2 ListNode head(0), *ret = &head; 3 int cin = 0, val; 4 while(l1 l2 cin){ 5 val = (l1? l1->val : 0) + (l2? l2->val : 0) + cin; 6 ret = ret->next = new ListNode(val % 10); 7 cin = val / 10; 8 if(l1) l1 = l1->next; 9 if(l2) l2 = l2->next; 10 } 11 return head.next; 12 } Remove Nth Node From End of List Given a linked list, remove the nth node from the end of list and return its head. (leetcode 19) 举例 : Given linked list: , and n = 2. After removing the second node from the end, the linked list becomes Note : Given n will always be valid. 两个指针 : 时间复杂度 O(n), 空间复杂度 O(1) 两个指针相距 n, 然后一起后移. 1 ListNode *removenthfromend(listnode *head, int n) { 2 if(!head n < 1) return head; 3 ListNode newhead(0); 4 newhead.next = head; 5 ListNode *last = &newhead, *first = &newhead; 6 while(n-- > 0 && last->next){ 7 last = last->next; 8 } 9 while(last->next){ 10 first = first->next; 11 last = last->next; 12 } 13 first->next = first->next->next;

19 1.3 基于链表的问题 return newhead. next; 15 } Merge k Sorted Lists Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. (leetcode 23) : 时间复杂度, 空间复杂度 O 这道题其实是 Merge two list 的延伸, 我们在 Merge 两个链表时候, 每次都是比较一下取较小的入新链表, 当有 k 个链表的时候, 我们也是取这 k 个节点最小的那个, 然后用它的 next 替换它, 再进行下次选取. 我们想一下, 最 naive 的做法是, 每次这 k 个都比较一遍, 那么这个每取一个的时间复杂度就是 O(k), 整个的时间复杂度就是 O(k*k*n). 其实这里面的过程有一个信息被我们遗漏了, 或者说是没有被利用好, 那就是当我们取出这 k 个节点最小那个时候, 其实除了最小的那个其他的节点之间大小关系也有比较过, 比如是节点 s 和 t, 然后新替入的那个进来之后, 再来一次取最小值, 这时候其实 s 和 t 的大小关系我们是已知的, 但是我们没有利用, 而是有可能还会取比较一次! 为了避免这个重复比较, 我们想到使用最小堆的方法 : 把这个 k 个节点放入最小堆, 然后取堆顶点, 再加入新节点, 整个操作只需要 O(lgk) 的时间复杂度, 所以整个的时间复杂度降为 O(k*nlgk) 1 ListNode *mergeklists(vector<listnode*> &lists) { 2 multimap<int, int> data; 3 ListNode ret(0), *pos = &ret; 4 for(int i = 0; i < lists.size(); i++){ 5 if(lists[i]) 6 data.insert(make_pair(lists[i]->val, i)); 7 } 8 while(!data.empty()){ 9 int index = data.begin()->second; 10 data.erase(data.begin()); 11 pos = pos->next = lists[index]; 12 lists[index] = lists[index]->next; 13 if(lists[index]) 14 data.insert(make_pair(lists[index]->val, index)); 15 } 16 return ret.next; 17 } 上面没有使用堆, 但是使用的是 tree map, 它其实可以用来模拟堆操作, 整个的时间复杂度和用堆是一样的 Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. (leetcode 24) 举例 : Given , you should return the list as Note : Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed. 两个指针 : 时间复杂度 O(n), 空间复杂度 O(1) 这题主要是考查细心程度, 还有对边界条件的考虑. 1 ListNode *swappairs(listnode *head) { 2 if(!head!head->next) return head; 3 ListNode newhead(0), *first = &newhead, *second = &newhead;

20 1.3 基于链表的问题 11 4 newhead.next = head; 5 while(first->next && first->next->next){ 6 second = first->next; 7 first->next = second->next; 8 second->next = first->next->next; 9 first->next->next = second; 10 first = second; 11 } 12 return newhead. next; 13 } Reverse Nodes in k-group Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. (leetcode 25) 举例 : Given this linked list: , For k = 2, you should return: , For k = 3, you should return: Note : If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is. You may not alter the values in the nodes, only nodes itself may be changed. Only constant memory is allowed. Care : 时间复杂度 O(n), 空间复杂度 O(1) 这题最保守的做法就是先算出链表的长度, 然后一段一段的反转. 1 ListNode *reversekgroup(listnode *head, int k) { 2 if(!head k <= 1) return head; 3 ListNode newhead(0), *first = &newhead, *second = &newhead, *pos = head; 4 newhead.next = head; 5 int count = 0; 6 while(pos){ 7 pos = pos->next; 8 count ++; 9 } 10 while(count >= k){ 11 second = first->next; 12 for(int i = 0; i < k - 1; i++){ 13 pos = second->next; 14 second->next = pos->next; 15 pos->next = first->next; 16 first->next = pos; 17 } 18 count = count - k; 19 first = second; 20 } 21 return newhead. next; 22 } Rotate List Given a list, rotate the list to the right by k places, where k is non-negative. (leetcode 61) 示例 : Given NULL and k = 2, return NULL.

21 1.3 基于链表的问题 12 Care : 时间复杂度 O(n), 空间复杂度 O(1) 首尾连接一下轻松搞定链表旋转 1 ListNode *rotateright(listnode *head, int k) { 2 if(!head k < 1) return head; 3 ListNode *pos = head, *pre = head; 4 int count = 0; 5 for(; count < k && pos; count++){ 6 pos = pos->next; 7 } 8 if(!pos) return rotateright(head, k%count); 9 while(pos->next){ 10 pos = pos->next; 11 pre = pre->next; 12 } 13 pos->next = head; 14 head = pre->next; 15 pre->next = NULL; 16 return head; 17 } Reorder List Given a singly linked list L: L 0 L 1... L n 1 L n, reorder it to: L 0 L n L 1 L n 1 L 2 L n 2...(leetcode 143) 反转 : 时间复杂度 O(n), 空间复杂度 O(1) 先获得链表的中间节点, 再对后半部分反转, 然后间隔插入到前半部分. 1 void reorderlist(listnode *head) { 2 if(!head!head->next) return; 3 ListNode *slow = head, *fast = head->next, *left = head, *right, * tmp; 4 while(fast && fast->next){ 5 slow = slow->next; 6 fast = fast->next->next; 7 } 8 right = slow->next; 9 while(right->next){ 10 tmp = right->next; 11 right->next = tmp->next; 12 tmp->next = slow->next; 13 slow->next = tmp; 14 } 15 right = slow->next; 16 slow->next = NULL; 17 while(left->next){ 18 tmp = right->next; 19 right->next = left->next; 20 left->next = right; 21 right = tmp; 22 left = left->next->next; 23 } 24 left->next = right; 25 }

22 1.3 基于链表的问题 Remove Linked List Elements Remove all elements from a linked list of integers that have value val. Example Given: ,val = 6 Return: (leetcode 203) 递归 : 时间复杂度 O(n), 空间复杂度 O(1) 递归写法很简洁, 虽然里面有三目运算符还有逗号表达式 1 ListNode* removeelements(listnode* head, int val) { 2 if(!head) return NULL; 3 return head->val == val? removeelements(head->next, val) : 4 (head->next = removeelements(head->next, val), head); 5 } Remove Duplicates from Sorted List Given a sorted linked list, delete all duplicates such that each element appear only once. (leetcode 83) 示例 : Given 1 1 2, return 1 2. Given , return 两个指针 : 时间复杂度 O(n), 空间复杂度 O(1) 遇到一样的就删除, 不一样就转移. 1 ListNode * deleteduplicates( ListNode * head) { 2 if(!head!head->next) return head; 3 ListNode *pre = head, *next = head->next; 4 while(next){ 5 if(pre->val == next->val){ 6 pre->next = next->next; 7 }else{ 8 pre = pre->next; 9 } 10 next = pre->next; 11 } 12 return head; 13 } Remove Duplicates from Sorted List II Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list. (leetcode 82) 示例 : Given , return Given , return 2 3. 两个指针 : 时间复杂度 O(n), 空间复杂度 O(1) 需要注意的就是如果最后几个是相同的也要删除.

23 1.3 基于链表的问题 14 1 ListNode * deleteduplicates( ListNode * head) { 2 if(!head!head->next) return head; 3 ListNode newhead(0); 4 ListNode *pre = &newhead, *next = head->next; 5 newhead.next = head; 6 while(next){ 7 if(pre->next->val == next->val){ 8 next = next->next; 9 }else{ 10 if(pre->next->next == next){ 11 pre = pre->next; 12 }else{ 13 pre->next = next; 14 } 15 next = next->next; 16 } 17 } 18 if(pre->next->next!= next) 19 pre->next = next; 20 return newhead. next; 21 } Partition List Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x. (leetcode 86) 举例 : Given and x = 3, return Care : 时间复杂度 O(n), 空间复杂度 O(1) 分别用两个链表指针维护小于 x 和大于 x 的, 最后再粘结到一起就行了. 1 ListNode *partition(listnode *head, int x) { 2 if(!head!head->next) return head; 3 ListNode less(0), greater(0), *pos = head; 4 ListNode *lpos = &less, *gpos = &greater; 5 while(pos){ 6 if(pos->val < x){ 7 lpos = lpos->next = pos; 8 }else{ 9 gpos = gpos->next = pos; 10 } 11 pos = pos->next; 12 } 13 gpos->next = NULL; 14 lpos->next = greater.next; 15 return less.next; 16 } Reverse Linked List II Reverse a linked list from position m to n. Do it in-place and in one-pass. (leetcode 92) 举例 : Given NULL, m = 2 and n = 4, return NULL.

24 1.3 基于链表的问题 15 Note : Given m, n satisfy the following condition: 1 m n length of list. Care : 时间复杂度 O(n), 空间复杂度 O(1) 这题主要是边界条件比较复杂, 如何写出优雅的囊括边界条件检测的代码是个问题. 1 ListNode *reversebetween(listnode *head, int m, int n) { 2 if(!head!head->next m >= n) return head; 3 ListNode newhead(0); 4 newhead.next = head; 5 ListNode *pos = &newhead, *next, *tmp; 6 for(int i = 0; i < m - 1; i++){ 7 pos = pos->next; 8 } 9 next = pos->next; 10 for(int i = 0; i < n - m; i++){ 11 tmp = next->next; 12 next->next = tmp->next; 13 tmp->next = pos->next; 14 pos->next = tmp; 15 } 16 return newhead. next; 17 } 使用一个新的头节点是一个很好的习惯 Copy List with Random Pointer A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null. Return a deep copy of the list. (leetcode 138) 哈希表 : 时间复杂度 O(n), 空间复杂度 O(n) 链表的缺点在于不能随机访问, 但是如果和哈希表来联合使用那么就可以做到随机访问了. 这道题在遍历链表时候, 一边遍历一边建立新的链表节点, 然后使用哈希表记录新老节点的映射关系, 等到再一次遍历老链表的时候根据映射表就可以快速的定位到新链表对应位置, 不必再从头开始找. 1 RandomListNode * copyrandomlist( RandomListNode * head){ 2 if(head == NULL) return NULL; 3 stack<randomlistnode*> to_be; 4 unordered_map < RandomListNode*, RandomListNode* > have_new; 5 RandomListNode* pos, *next, *rand; 6 to_be.push(head); 7 pos = head; 8 RandomListNode* new_head, *new_pos, *new_next, *new_rand; 9 new_head = new RandomListNode(head->label); 10 new_pos = new_head; 11 have_new[pos] = new_pos; 12 while(!to_be.empty()){ 13 pos = to_be.top(); 14 to_be.pop(); 15 next = pos->next; 16 rand = pos->random; 17 new_pos = have_new[pos]; 18 if(next!= NULL){ 19 if(have_new.count(next) == 1){ 20 new_pos->next = have_new[next]; 21 } 22 else{

25 1.3 基于链表的问题 new_next = new RandomListNode(next->label); 24 new_pos->next = new_next; 25 have_new[next] = new_next; 26 to_be.push(next); 27 } 28 }else{ 29 new_pos->next = NULL; 30 } 31 if(rand!= NULL){ 32 if(have_new.count(rand) == 1){ 33 new_pos->random = have_new[rand]; 34 } 35 else{ 36 new_rand = new RandomListNode(rand->label); 37 new_pos->random = new_rand; 38 have_new[rand] = new_rand; 39 to_be.push(rand); 40 } 41 }else{ 42 new_pos->random = NULL; 43 } 44 } 45 return new_ head; 46 } 插入法 : 时间复杂度 O(n), 空间复杂度 O(1) 插入法实质上也是在建立新老节点的对应关系, 不像哈希表这种需要额外空间的对应, 而是非常巧妙的在老链表的结构上建立映射关系, 举重若轻. 1 RandomListNode * copyrandomlist( RandomListNode * head){ 2 if(!head) return NULL; 3 RandomListNode *pos = head, *tmp, *ret; 4 while(pos){ 5 tmp = new RandomListNode(pos->label); 6 tmp->next = pos->next; 7 pos->next = tmp; 8 pos = pos->next->next; 9 } 10 pos = head; 11 while(pos){ 12 pos->next->random = pos->random? pos->random->next : NULL; 13 pos = pos->next->next; 14 } 15 pos = head; 16 ret = pos->next; 17 while(pos){ 18 tmp = pos->next; 19 pos->next = tmp->next; 20 tmp->next = pos->next? pos->next->next : NULL; 21 pos = pos->next; 22 } 23 return ret; 24 } 其实这道题你还可以这样看, 这里链表的节点有个 next 指针和 random 指针, 其实这就是一个图了 ( 链表是特殊的树, 树是特殊的图 ), 所以套用图的 DFS 和 BFS, 你在处理过程中就有两种选择 : 比如你建立老链表第一个节点的对应新节点后, 你可以类似 DFS 选择直接深度

26 1.3 基于链表的问题 17 优先的访问 next 节点 next 的 next 节点等等 ; 同样你也可以类似 BFS 下次访问 next 节点和 rand 节点. 所以你可以使用递归的方式实现.

27 第 2 章树 2.1 二叉树的遍历 二叉树的遍历是解决很多二叉树问题的基础, 它的递归写法和非递归写法更是需要都要掌握的, 这里的遍历就是将树的节点都依次访问一遍, 因为访问顺序的问题, 就可以分为前序, 中序, 后序以及层次等很多种遍历的方法 PreOrederTraversal Given a binary tree, return the preorder traversal of its nodes values.(leetcode 144) 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 前序遍历的递归写法, 非常简单, 只需要先访问跟节点, 再递归的执行左子树和右子树 1 void BiTree:: PreOrderTraversal( TreeNode* root){ 2 if(!root) return; 3 cout<<root->val<<endl; 4 if(root->left) 5 PreOrderTraversal(root->left); 6 if(root->right) 7 PreOrderTraversal(root->right); 8 } 栈式迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 使用栈来模拟递归过程也是很显而易见的, 具体做法就是先访问根节点, 然后先让右孩子入栈接着是左孩子, 然后左孩子出栈后重复这个过程 1 void BiTree:: PreOrderTraversal( TreeNode* root){ 2 if(!root) return; 3 stack<treenode*> s; 4 TreeNode *cur; 5 s.push(root); 6 while(!s.empty()){ 7 cur = s.top(); 8 s.pop(); 9 cout<<cur->val<<endl; 10 if(cur->right) 11 s.push(cur->right); 12 if(cur->left) 13 s.push(cur->left); 14 } 15 } 18

28 2.1 二叉树的遍历 19 Mirror 迭代 : 时间复杂度 O(n), 空间复杂度 O(1) Mirror 迭代法是经过 Lee 介绍过来的, 非常的迷人, 它的做法就是在遍历的过程中, 访问了当前节点之后, 先找当前节点的前驱并让此前驱的右孩子指向它, 再访问它的左孩子并重复这个过程 在此之后会访问到它前驱然后再次回到当前节点, 此时再次试图建立前驱, 发现已经建立了, 这就说明当前节点左边已经全部遍历完, 则继续访问当前节点的右边节点, 不断的重复此过程 1 void BiTree:: PreOrderTraversal( TreeNode* root){ 2 if(!root) return; 3 TreeNode *curr = root, *next; 4 while(curr){ 5 next = curr->left; 6 if(!next){ 7 cout<<curr->val<<endl; 8 curr = curr->right; 9 continue; 10 } 11 while(next->right && next->right!= curr){ 12 next = next->right; 13 } 14 if(next->right == curr){ 15 next->right = NULL; 16 curr = curr->right; 17 }else{ 18 cout<<curr->val<<endl; 19 next->right = curr; 20 curr = curr->left; 21 } 22 } 23 } 这个 Mirror 算法一旦掌握后, 威力无穷, 你可以用它方便的建立二叉树前序索引并且遇到那些要求用迭代来实现的二叉树问题也可以很快的写出来 InOrederTraversal Given a binary tree, return the inorder traversal of its nodes values. (leetcode 94) 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 void BiTree::InOrderTraversal(TreeNode* root){ 2 if(!root) return; 3 if(root->left) 4 InOrderTraversal(root->left); 5 cout<<root->val<<endl; 6 if(root->right) 7 InOrderTraversal(root->right); 栈式迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 这里需要说一下的是, 数据结构那本书上写了两种栈式迭代方法, 这是其中之一, 使用两重循环的那个 1 void BiTree::InOrderTraversal(TreeNode* root){ 2 vector<int> data; 3 if(!root) return data; 4 stack<treenode*> s;

29 2.1 二叉树的遍历 20 5 TreeNode *pos = root; 6 while(!s.empty() pos){ 7 while(pos){ 8 s.push(pos); 9 pos = pos->left; 10 } 11 pos = s.top(); 12 s.pop(); 13 std::cout<<pos->val<<std::endl; 14 pos = pos->right; // 这个非常重要 15 } 16 } 栈式迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 这里需要说一下的是, 数据结构那本书上写了两种栈式迭代方法, 这是其中之二, 使用一重循环, 实际上是一样的 1 void BiTree::InOrderTraversal(TreeNode* root){ 2 vector<int> data; 3 if(!root) return data; 4 stack<treenode*> s; 5 TreeNode *pos = root; 6 while(!s.empty() pos){ 7 if(pos){ 8 s.push(pos); 9 pos = pos->left; 10 }else{ 11 pos = s.top(); 12 s.pop(); 13 std::cout<<pos->val<<std::endl; 14 pos = pos->right; // 这个非常重要 15 } 16 } 17 } Mirror 迭代 : 时间复杂度 O(n), 空间复杂度 O(1) 这里 Mirror 方法和前序的 Mirror 方法基本一样, 唯一的区别就是打印当前值的时机 1 void BiTree::InOrderTraversal(TreeNode* root){ 2 if(!root) return; 3 TreeNode *curr = root, *next; 4 while(curr){ 5 next = curr->left; 6 if(!next){ 7 cout<<curr->val<<endl; 8 curr = curr->right; 9 continue; 10 } 11 while(next->right && next->right!= curr){ 12 next = next->right; 13 } 14 if(next->right == curr){ 15 next->right = NULL; 16 cout<<curr->val<<endl; 17 curr = curr->right; 18 }else{ 19 next->right = curr;

30 2.1 二叉树的遍历 curr = curr->left; 21 } 22 } 23 } PostOrederTraversal Given a binary tree, return the postorder traversal of its nodes values. (leetcode 145) 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 void BiTree:: PostOrderTraversal( TreeNode* root){ 2 if(!root) return; 3 if(root->left) 4 PostOrderTraversal(root->left); 5 if(root->right) 6 PostOrderTraversal(root->right); 7 cout<<root->val<<endl; 8 } 栈式迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 这里判断一个节点的孩子是否被访问过的方法是 : 记录上一次打印的节点, 如果上一次打印的节点是它的孩子节点, 那么必然它的所有孩子及其子树都访问完了, 换句话说该访问它本身了. 1 void BiTree:: PostOrderTraversal( TreeNode* root){ 2 if(!root) return; 3 stack<treenode*> s; 4 TreeNode *pos = root, *last = root; 5 s.push(root); 6 while(!s.empty()){ 7 pos = s.top(); 8 if(pos->left == last pos->right == last (!pos->left &&! pos->right)){ 9 // 孩子已经打印完毕或者根本就没有孩子 10 cout<<pos->val<<endl; 11 last = pos; 12 s.pop(); 13 }else{ 14 if(pos->right){ 15 s.push(pos->right); 16 } 17 if(pos->left){ 18 s.push(pos->right); 19 } 20 } 21 } 22 } 这里没有出现万众期待的 Mirror 算法, 主要是后序使用 Mirror 很复杂, 我暂时还没有想到怎么实现 ˆ ˆ LevelOrderTraversal Given a binary tree, return the level order traversal of its nodes values. (ie, from left to right, level by level). (leetcode 145)

31 2.1 二叉树的遍历 22 队列 : 时间复杂度 O(n), 空间复杂度 O(w), w 为二叉树最大宽度 使用队列, 上一层进入队列, 然后添加下一层, 直到不再有节点进入队列 1 vector<vector<int> > levelorder(treenode *root) { 2 vector<vector<int> > data; 3 if(!root) return data; 4 queue<treenode*> cur; 5 cur.push(root); 6 int size = 0; 7 TreeNode* now; 8 while(!cur.empty()){ 9 vector<int> tmp; 10 size = cur.size(); 11 for(int i = 0; i < size; i++){ 12 now = cur.front(); 13 cur.pop(); 14 tmp.push_back(now->val); 15 if(now->left) 16 cur.push(now->left); 17 if(now->right) 18 cur.push(now->right); 19 } 20 data.push_back(tmp); 21 } 22 return data; 23 } 其实层次遍历除了这个先上而下, 先左而右的顺序以外还有很多顺序, 但是都可以通过这个顺序来转换, 所以就不再仔细讨论 Recover Binary Search Tree Two elements of a binary search tree (BST) are swapped by mistake. Recover the tree without changing its structure. (leetcode 99) BST 树的中序遍历是排序好的, 那么可以通过中序遍历来看一下哪两个地方发生了交换, 发生交换的地方必然是前面那个数比后面的大, 只要在遍历过程记录这个位置就可以了. 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 这段代码很有技巧, 需要细细品读. 对于 1,5,3,4,2 这个序列 : fisrt 是 5,second 是 2; 对于 1,3,2,4,5 这个序列 : first 是 3, second 是 2. 1 void dfs(treenode* root, int& last, TreeNode* &first, TreeNode* &second) { 2 if(root->left){ 3 dfs(root->left, last, first, second); 4 } 5 if(root->val < last){ 6 second = root; 7 } 8 if(!second){ 9 first = root; 10 } 11 last = root->val; 12 if(root->right){ 13 dfs(root->right, last, first, second); 14 } 15 } 16

32 2.1 二叉树的遍历 void recovertree(treenode* root) { 18 if(!root) return; 19 TreeNode *first = NULL, *second = NULL; 20 int min = (-1)<<31; 21 dfs(root, min, first, second); 22 int tmp = first->val; 23 first->val = second->val; 24 second->val = tmp; 25 } 迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 void recovertree(treenode* root) { 2 if(!root) return; 3 TreeNode *first = NULL, *second = NULL, *cur; 4 int last = INT_MIN; 5 stack<treenode*> s; 6 s.push(root); 7 while(!s.empty()){ 8 cur = s.top(); 9 while(cur){ 10 s.push(cur->left); 11 cur = cur->left; 12 } 13 s.pop(); 14 if(!s.empty()){ 15 cur = s.top(); 16 if(cur->val < last){ 17 second = cur; 18 } 19 if(!second){ 20 first = cur; 21 } 22 last = cur->val; 23 s.pop(); 24 s.push(cur->right); 25 } 26 } 27 int tmp = first->val; 28 first->val = second->val; 29 second->val = tmp; 30 }

33 2.2 二叉树的建立 二叉树的建立 Construct Binary Tree from Preorder and Inorder Traversal Given preorder and inorder traversal of a tree, construct the binary tree. (leetcode 105) Note: You may assume that duplicates do not exist in the tree. 递归 : 时间复杂度 O(nlgn), 空间复杂度 O(lgn) 前序序列的第一个元素肯定是树的根节点, 而且使用这个值在中序序列中查找, 找到的那个位置之前的必然是左子树, 之后的必然是右子树, 所以根据这个特点就可以很容易的使用递归的做法解题 1 TreeNode* detail(vector<int> &inorder, vector<int> &preorder, 2 int in_start, int in_end, int pre_start, int pre_end){ 3 //inorder[in_start, in_end], preorder[pre_start, pre_end] 4 if(in_start > in_end pre_start > pre_end) return NULL; 5 int val = preorder[pre_start]; 6 7 TreeNode* father = new TreeNode(val); 8 TreeNode *left = NULL, *right = NULL; 9 10 int in_pos = in_start, pre_pos = pre_start; 11 for(; in_pos <= in_end; in_pos++, pre_pos++){ 12 if(val == inorder[in_pos]) 13 break; 14 } left = detail(inorder, preorder, in_start, in_pos-1, pre_start+1, pre_pos); 17 right = detail(inorder, preorder, in_pos+1, in_end, pre_pos+1, pre_end); father->left = left; 20 father->right = right; return father; 23 } TreeNode *buildtree(vector<int> &preorder, vector<int> &inorder) { 26 int size = inorder.size(); 27 if(size == 0) return NULL; 28 return detail(inorder, preorder, 0, size - 1, 0, size - 1); 29 } Construct Binary Tree from Postorder and Inorder Traversal Given postorder and inorder traversal of a tree, construct the binary tree. (leetcode 106) Note: You may assume that duplicates do not exist in the tree. 递归 : 时间复杂度 O(nlgn), 空间复杂度 O(lgn) 后序序列的最后一个元素肯定是树的根节点, 而且使用这个值在中序序列中查找, 找到的那个位置之前的必然是左子树, 之后的必然是右子树, 所以根据这个特点就可以很容易的使用递归的做法解题

34 2.2 二叉树的建立 25 1 TreeNode* recursion(vector<int> &inorder, vector<int> &postorder, 2 int s_in, int e_in, int s_po, int e_po){ 3 //inorder[s_in, e_in],postorder[s_po, e_po] 4 if(s_in > e_in) return NULL; 5 if(s_in == e_in) return (new TreeNode(inorder[s_in])); 6 TreeNode *root = new TreeNode(postorder[e_po]); 7 int split_in, split_po; 8 for(split_in = s_in; split_in <= e_in; split_in++){ 9 if(postorder[e_po] == inorder[split_in]) 10 break; 11 } 12 split_po = s_po + split_in - s_in; 13 root->left = recursion(inorder, postorder, s_in, split_in - 1, s_po, split_po - 1 ); 14 root->right = recursion(inorder, postorder, split_in + 1, e_in, split_po, e_po - 1); 15 return root; 16 } TreeNode *buildtree(vector<int> &inorder, vector<int> &postorder) { 19 if(inorder.size()!= postorder.size() inorder.size() == 0) 20 return NULL; 21 return recursion(inorder, postorder, 0, inorder.size() - 1, 0, postorder.size() - 1); 22 } Convert Sorted Array to Binary Search Tree Given an array where elements are sorted in ascending order, convert it to a height balanced BST. (leetcode 108) 递归 : 时间复杂度 O(n), 空间复杂度 O(n) 建立平衡的二叉树, 那么我们每次取数组的中间位置那个元素为根节点, 然后它之前的部分创建左子树, 之后的部分创建右子树, 那么很容易就可以使用递归实现. 1 TreeNode* dfs(vector<int> &num, int start, int end){ 2 if(start > end) return NULL; 3 if(start == end) return (new TreeNode(num[start])); 4 int mid = (start + end) / 2; 5 TreeNode* root = new TreeNode(num[mid]); 6 root->left = dfs(num, start, mid - 1); 7 root->right = dfs(num, mid + 1, end); 8 return root; 9 } TreeNode *sortedarraytobst(vector<int> &num) { 12 int size = num.size(); 13 return dfs(num, 0, size - 1); 14 } 因为是数组, 所以可以很方便的找到中位数, 但是如果是链表, 则需要使用一些小技巧 Convert Sorted List to Binary Search Tree Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST. (leetcode 109)

35 2.2 二叉树的建立 26 递归 : 时间复杂度 O(n), 空间复杂度 O(n) 前面使用数组构造 BST 树, 我们可以看到每次需要求出它的中间的那个数, 然后以它创建根节点, 但是对于有序链表来说, 找到中位数起码要花 O(n) 时间, 那么这样算下来整个程序需要 O(nlgn) 的时间! 这似乎和数组的 O(n) 差别比较大 我们可以想一下, 摒弃这种自上而下的思维, 来一次自下而上的方法 : 我们先构建左子树, 构建完了之后访问的最后一点节点是不是就是根节点的前驱? 这样我们记下这个前驱, 然后一记 next 是不是就求出我们梦寐以求的中位数那个节点! 然后再 next 一下, 构建右子树, 看看发生了什么? 我们一边建树一边就得到中间节点, 所以就省掉了找中间节点的那个时间! 1 TreeNode* DFS(ListNode* &head, int n){ 2 if(n == 0) return NULL; 3 TreeNode *root; 4 if(n == 1){ 5 root = new TreeNode(head->val); 6 head = head->next; 7 return root; 8 } 9 TreeNode *left = DFS(head, n/2);// head travel the list 10 root = new TreeNode(head->val); 11 head = head->next; 12 TreeNode *right = DFS(head, n n/2 ); 13 root->left = left; 14 root->right = right; 15 return root; 16 } TreeNode * sortedlisttobst( ListNode * head) { 19 if(!head) return NULL; 20 int count = 0; 21 ListNode *pos = head; 22 while(pos){//compute the length of the list 23 pos = pos->next; 24 count ++; 25 } 26 return DFS(head, count); 27 } 这里使用自下而上的做法很具有普遍性, 我们从上面看得不到的东西, 从下面可以积累到 Unique Binary Search Trees Given n, how many structurally unique BST s (binary search trees) that store values 1...n? (leetcode 96) 这道题虽然是一个二叉树的问题, 但是其实是数学归纳法的问题, 我们可以得到递推公式 : S(0) = 1 n 1 S(n) = S(i) S(n 1 i) i=0 (2.1) 这是因为对于有 n 个元素的 BST(1,2,...,n), 我们考虑由谁来作为根节点, 如果以 i+1 为根节点, 那么左子树为 (1,2,...,i), 右子树为 (i+2,i+3,...,n), 所以可以得到上面的递推关系. 迭代 : 时间复杂度 O(n 2 ), 空间复杂度 O(n) 1 int numtrees(int n) { 2 if(!n) return 0;

36 2.2 二叉树的建立 27 3 vector<int> num(n+1, 0); 4 num[1] = 1; 5 num[2] = 2; 6 for(int i = 3; i <= n; i++){ 7 num[i] = 2*num[i-1]; 8 for(int j = 1; j < i; j++){ 9 num[i] = num[i] + num[j]*num[i-1-j]; 10 } 11 } 12 return num[n]; 13 } Unique Binary Search Trees II Given n, generate all structurally unique BST s (binary search trees) that store values 1...n. (leetcode 95) : 时间复杂度 TODO, 空间复杂度 TODO 每次对于构造序列 (i,i+1,...,j), 切分 j-i+1 次, 然后分别递归构造. 1 vector<treenode*> generate(int start, int end){ 2 if(start == end){ 3 return vector<treenode*>(1, new TreeNode(start)); 4 } 5 vector<treenode*> data; 6 if(start > end){ 7 data.push_back(null); 8 return data; 9 } 10 for(int i = start; i <= end; i++){ 11 TreeNode* root; 12 vector<treenode*> left = generate(start, i - 1); 13 vector<treenode*> right = generate(i + 1, end); 14 for(int j = 0; j < left.size(); j++){ 15 for(int k = 0; k < right.size(); k++){ 16 root = new TreeNode(i); 17 root->left = left[j]; 18 root->right = right[k]; 19 data.push_back(root); 20 } 21 } 22 } 23 return data; 24 } vector<treenode*> generatetrees(int n) { 27 return generate(1, n); 28 }

37 2.3 二叉树的属性 二叉树的属性 Validate Binary Search Tree Given a binary tree, determine if it is a valid binary search tree (BST). (leetcode 98) 判断一个二叉树是否是合法的 BST, 我们可以想到 BST 树的中序序列是非减序列, 于是我们可以使用中序遍历这颗二叉树, 在遍历的过程中查看是否有反常的数据. 当然, 根据上面说的三种中序遍历的方法, 这里同样有三种解法. 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 bool dfs(treenode *root, int& up){ 2 if(!root) return true; 3 if(root->left){ 4 bool left = dfs(root->left, up); 5 if(!left) return false; 6 } 7 if(root->val <= up && (MIN up!= (-1)<<31)){ 8 return false; 9 } 10 if(root->val == (-1)<<31) 11 MIN = true; 12 up = root->val; 13 if(root->right){ 14 bool right = dfs(root->right, up); 15 if(!right) return false; 16 } 17 return true; 18 } bool isvalidbst(treenode *root) { 21 if(!root) return true; 22 int up = (-1)<<31; 23 MIN = false; 24 return dfs(root, up); 25 } 这里可以看到一些边界条件的判断, 显得有点复杂, 其实就是简单的中序遍历 栈式迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 bool isvalidbst(treenode *root) { 2 if(!root) return false; 3 stack<treenode*> s; 4 TreeNode *p = root; 5 s.push(root); 6 while(p->left){ 7 s.push(p->left); 8 p = p->left; 9 } 10 int last = p->val; 11 s.pop(); 12 if(p->right){ 13 s.push(p->right); 14 p = p->right; 15 while(p->left){ 16 s.push(p->left); 17 p = p->left; 18 }

38 2.3 二叉树的属性 } 20 while(!s.empty()){ 21 p = s.top(); 22 s.pop(); 23 if(last >= p->val) return false; 24 last = p->val; 25 if(p->right){ 26 s.push(p->right); 27 p = p->right; 28 while(p->left){ 29 s.push(p->left); 30 p = p->left; 31 } 32 } 33 } 34 return true; 35 } Mirror 迭代 : 时间复杂度 O(n), 空间复杂度 O(1) 这里使用 Mirror 建立线索然后进行中序遍历, 在中序遍历的同时进行判断 1 bool isvalidbst(treenode *root) { 2 if(!root) return true; 3 TreeNode *curr = root, *next; 4 int last = INT_MIN; 5 bool isfirst = true; 6 bool ret = true; 7 while(curr){ 8 if(!curr->left){ 9 if(!isfirst && curr->val <= last){ 10 ret = false; 11 } 12 if(isfirst) 13 isfirst = false; 14 last = curr->val; 15 curr = curr->right; 16 continue; 17 } 18 next = curr->left; 19 while(next->right){ 20 if(next->right == curr) break; 21 next = next->right; 22 } 23 if(next->right == curr){ 24 next->right = NULL; 25 if(!isfirst && curr->val <= last){ 26 ret = false; 27 } 28 if(isfirst) 29 isfirst = false; 30 last = curr->val; 31 curr = curr->right; 32 }else{ 33 next->right = curr; 34 curr = curr->left; 35 } 36 } 37 return ret;

39 2.3 二叉树的属性 } 有了 Mirror 算法, 是不是你已经爱上它了, 再也不用栈这么麻烦了, 不过有一点需要注意的是一旦你使用 Mirror 算法, 那么必须保证把整个树全遍历一遍, 不能中途退出, 因为那样树的结构被改变了 Symmetric Tree Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center). (leetcode 101) 这是求证树是不是自身 Mirror( 成镜像 ). 队列 : 时间复杂度 O(n), 空间复杂度 O(w), w 为树的最大宽度 1 bool issymmetric(treenode* root){ 2 if(!root) return true; 3 deque<treenode*> left(1, root->left), right(1, root->right); 4 TreeNode *l, *r; 5 while(!left.empty() &&!right.empty()){ 6 l = left.front(); 7 r = right.front(); 8 left.pop_front(); 9 right.pop_front(); 10 if(!l &&!r) continue; 11 if(!l!r l->val!= r->val) return false; 12 left.push_back(l->left); 13 left.push_back(l->right); 14 right.push_back(r->right); 15 right.push_back(r->left); 16 } 17 return true; 18 } 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 这里是把一棵树的对称问题看成两棵树的对称问题 1 bool recursion(treenode* root, TreeNode* symm){ 2 if(!root &&!symm) 3 return true; 4 if(!root!symm) return false; 5 if(root->val!= symm->val) return false; 6 if(root == symm) return recursion(root->left, symm->right); 7 return recursion(root->left, symm->right) && recursion(root->right, symm->left); 8 } 9 10 bool issymmetric(treenode* root){ 11 if(!root) return true; 12 return recursion(root, root); 13 } 这里还可以延伸出一个 求一个二叉树的镜像树 Same Tree Given a binary tree, determine if it is height-balanced. (leetcode 110)

40 2.3 二叉树的属性 31 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 先判断左子树是否高度平衡并返回左子树高度, 再判断右子树是否高度平衡, 再返回右子树高度, 根据左右子树高度再判断当前树是否平衡. 1 bool dfs(treenode *root, int &hight){ 2 if(!root){ 3 hight = 0; 4 return true; 5 } 6 int left, right; 7 bool is_left = dfs(root->left, left); 8 bool is_right = dfs(root->right, right); 9 hight = left > right? left + 1 : right + 1; 10 return is_left && is_right && (abs(left - right) < 2); 11 } bool isbalanced(treenode *root) { 14 int hight; 15 return dfs(root, hight); 16 } Maximum Depth of Binary Tree Given a binary tree, find its maximum depth.(leetcode 104) 从根节点来看, 它的深度就是左右子树深度较大的那个 +1, 所以很自然的想到递归 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 递归代码十分简洁 1 int maxdepth(treenode *root){ 2 if(!root) return 0; 3 int left = maxdepth(root->left); 4 int right = maxdepth(root->right); 5 return left < right? right + 1 : left + 1; 6 } 除了递归, 其实这道题能不能用迭代的做法呢? 答案是肯定的, 最初你可能会想到用两个栈, 一个栈存放节点, 一个栈存放深度, 其实可以把这个两者打包成一个 pair, 使用一个栈就可以啦 迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 int maxdepth(treenode *root) { 2 if(!root) return 0; 3 stack<pair<treenode*, int> > s; 4 s.push(make_pair(root, 1)); 5 pair<treenode*, int> curr; 6 int result = INT_MIN; 7 while(!s.empty()){ 8 curr = s.top(); 9 s.pop(); 10 if(!curr.first->left &&!curr.first->right){ 11 if(result < curr.second) 12 result = curr.second; 13 continue; 14 } 15 if(curr.first->left){

41 2.3 二叉树的属性 s.push(make_pair(curr.first->left, curr.second + 1)); 17 } 18 if(curr.first->right){ 19 s.push(make_pair(curr.first->right, curr.second + 1)); 20 } 21 } 22 return result; 23 } 这种自上而下的过程 ( 比如节点深度 ), 用迭代实现就很简单, 但是如果是自下而上的过程呢? 好像很复杂, 你有什么好的想法? Minimum Depth of Binary Tree Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. (leetcode 111) 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 自下而上的递归, 非常的简单 1 int mindepth(treenode *root) { 2 if(!root) return 0; 3 if(!root->left &&!root->right) return 1; 4 if(!root->left) 5 return mindepth(root->right) + 1; 6 if(!root->right) 7 return mindepth(root->left) + 1; 8 return min(mindepth(root->left), mindepth(root->right)) + 1; 9 } DFS : 时间复杂度 O(n), 空间复杂度 O(lgn) 这也是递归, 但是是一种自上而下的递归, 可以进行剪枝而不必把整个树都访问一遍 1 void dfs(treenode *root, int &result, int depth){ 2 if(result < depth + 1) return; 3 if(!root->left &&!root->right){ 4 result = depth + 1; 5 return; 6 } 7 if(root->left) 8 dfs(root->left, result, depth + 1); 9 if(root->right) 10 dfs(root->right, result, depth + 1); 11 } int mindepth(treenode *root) { 14 if(!root) return 0; 15 int result = INT_MAX; 16 dfs(root, result, 0); 17 return result; 18 } 迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 同样我们也可以剪枝

42 2.3 二叉树的属性 33 1 int mindepth(treenode *root) { 2 if(!root) return 0; 3 stack<pair<treenode*, int> > s; 4 s.push(make_pair(root, 1)); 5 int result = -((1<<31) + 1); 6 TreeNode *node; 7 int depth; 8 while(!s.empty()){ 9 node = s.top().first; 10 depth = s.top().second; 11 s.pop(); 12 if(result < depth) continue; 13 if(!node->left &&!node->right) 14 result = depth; if(node->left ) 17 s.push(make_pair(node->left, depth + 1)); 18 if(node->right) 19 s.push(make_pair(node->right, depth + 1)); 20 } 21 return result; 22 } Path Sum Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum. (leetcode 112) 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 先减去当前节点的值, 剩余的值再分别递归求解左右子树 1 bool haspathsum(treenode *root, int sum) { 2 if(root == NULL) return false; 3 int val = root->val; 4 sum = sum - val; 5 if(sum == 0 &&!root->left &&!root->right) 6 return true; 7 8 bool left = false; 9 if(root->left){ 10 left = haspathsum(root->left, sum); 11 } 12 if(left) return true; 13 if(root->right) 14 return haspathsum(root->right, sum); 15 return false; 16 } 仿照最大 / 小深度问题, 可以在迭代栈加上附加路径和这个信息, 那么实现起来就非常简单了 迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 bool haspathsum(treenode *root, int sum) { 2 if(!root) return false; 3 stack<pair<treenode*, int> > s; 4 s.push(make_pair(root, root->val));

43 2.3 二叉树的属性 34 5 TreeNode *node; 6 int e; 7 while(!s.empty()){ 8 node = s.top().first; 9 e = s.top().second; 10 s.pop(); 11 if(!node->left &&!node->right && e == sum) 12 return true; 13 if(node->left) 14 s.push(make_pair(node->left, e + node->left->val)); 15 if(node->right) 16 s.push(make_pair(node->right, e + node->right->val)); 17 } 18 return false; 19 } 可以看得出, 迭代栈加入附件信息是一个常用的技巧, 需要深刻理解和掌握. 另外, 这道题还可以变形为求二叉树最远两个节点距离的问题, 这就相当于把每个节点的权值都设为 1, 方法和这道题一样, 而且还要比它简单 Path Sum II Given a binary tree and a sum, find all root-to-leaf paths where each path s sum equals the given sum. (leetcode 113) 这题是 Path Sum 问题的延续, 解法其实是一模一样的. 递归 : 时间复杂度 O(n), 空间复杂度 O(n) 这里有个技巧需要注意的就是, 可以使用一个引用参数 cur 来记录跟节点到当前节点这条路径中的所有值, 我们在进入某个节点后 cur 要 push 这个节点, 在离开这个节点后就要 pop 这个节点 1 vector<vector<int> > pathsum(treenode *root, int sum){ 2 vector<int> cur; 3 vector<vector<int> > result; 4 recursion(root, cur, result, sum); 5 return result; 6 } 7 8 void recursion(treenode *root, vector<int> &cur, vector<vector<int> > & result, int sum){ 9 if(!root) return; 10 cur.push_back(root->val); 11 if(!root->left &&!root->right && root->val == sum) 12 result.push_back(cur); 13 if(root->left) 14 recursion(root->left, cur, result, sum - root->val); 15 if(root->right) 16 recursion(root->right, cur, result, sum - root->val); 17 cur.pop_back(); 18 } 我们知道 Path Sum 有迭代的做法, 但是那种通过栈附加信息的做法对于我们这题还要求求出路径的问题不是很适合, 所以就没有写出这种做法了, 你有什么好想法么? Binary Tree Maximum Path Sum Given a binary tree, find the maximum path sum.the path may start and end at any node in the tree. (leetcode 124)

44 2.3 二叉树的属性 35 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 这里先说约定一些叫法 : 1 最大路径和 : max{ 树中任意两节点之间的路径和 } 2 最大到根路径和 : max{ 树中任意节点到根节点的路径和 } 这里我们知道, 这个路径肯定是有个拐点, 那么这个拐点肯定是某个子树的根节点, 所以我们只要递归的求每个子树的过根最大路径和, 然后在这些路径和中选出最大的那个就可以了. 过根最大路径和怎么求呢? 可以先分别求出左右子树的最大到根路径和, 根据这个两个路径和与根节点则可以求出当前树的最大路径和. 1 int dfs(treenode *root, int &result){ 2 if(!root) return 0; 3 if(!root->left &&!root->right){ 4 if(result < root->val) 5 result = root->val; 6 return root->val > 0? root->val : 0; 7 } 8 int left = 0, right = 0; 9 if(root->left){ 10 left = dfs(root->left, result); 11 } 12 if(root->right){ 13 right = dfs(root->right, result); 14 } 15 int cur = root->val + left + right; 16 if(result < cur) 17 result = cur; 18 int add = left > right? left : right; 19 if(add < 0) return root->val > 0? root->val : 0; 20 return root->val + add > 0? root->val + add : 0; 21 } int maxpathsum(treenode *root) { 24 int result = INT_MIN; 25 dfs(root, result); 26 return result; 27 } Sum Root to Leaf Numbers Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number.an example is the root-to-leaf path which represents the number 123.Find the total sum of all root-to-leaf numbers. (leetcode 129) 其实这还是属于那种自上而下带有附加信息过来的问题, 和最大 / 小深度问题是一样的. 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 从上面传来上面路径产生的数字, 类似于传来上面路径的深度 ( 最大深度问题 ) 和上面路径的和 (Path Sum 问题 ) 1 void dfs(treenode *root, int track, int &sum){ 2 if(!root) return; 3 track = track*10 + root->val; 4 if(!root->left &&!root->right){ 5 sum = sum + track;

45 2.3 二叉树的属性 36 6 } 7 if(root->left) 8 dfs(root->left, track, sum); 9 if(root->right) 10 dfs(root->right, track, sum); 11 } int sumnumbers(treenode *root){ 14 int sum = 0; 15 int track = 0; 16 dfs(root, track, sum); 17 return sum; 18 } 同样, 类似与 Path Sum 这类问题, 可以常用附加路径信息的栈实现迭代, 具体做法就是当前节点左右孩子入栈时候当前节点的附加信息值 *10 加左右孩子节点的值然后作为左右孩子的附加信息值, 这里就不再写出 Least Common Ancest Given a binary tree, return the least common ancest of two nodes. 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 TreeNode * NearestCommAncestor( TreeNode * root, TreeNode * node1, TreeNode *node2){ 2 if(!root!node1!node2) return NULL; 3 if(node1 == root node2 == root) 4 return root; 5 if(!root->left) 6 return NearestCommAncestor(root->right, node1, node2); 7 if(!root->right) 8 return NearestCommAncestor(root->left, node1, node2); 9 TreeNode *left, *right; 10 left = NearestCommAncestor(root->left, node1, node2); 11 right = NearestCommAncestor(root->right, node1, node2); 12 if(left && right) return root; 13 return left? left : right; 14 }

46 2.4 其他 其他 Flatten Binary Tree to Linked List Given a binary tree, flatten it to a linked list in-place by the pre-order. (leetcode 114) 这是一个基于先序遍历的问题, 所以可以使用递归和迭代的方法. 递归 : 时间复杂度 O(n), 空间复杂度 O(lgn) 1 void flatten(treenode *root) { 2 TreeNode *tail; 3 recursion(root, tail); 4 } 5 6 TreeNode* recursion(treenode *root, TreeNode* &tail){ 7 if(!root) return NULL; 8 TreeNode *next = NULL; 9 tail = root; 10 if(root->left) 11 next = recursion(root->left, tail); 12 if(root->right) 13 tail->right = recursion(root->right, tail); 14 root->left = NULL; 15 if(next) 16 root->right = next; 17 return root; 18 } 迭代 : 时间复杂度 O(n), 空间复杂度 O(lgn) 这里就是完完全全的迭代版前序遍历, 这里使用了栈, 同样你也可以使用 Mirror 算法. 1 void flatten(treenode *root) { 2 if(!root) return; 3 stack<treenode*> s; 4 s.push(root); 5 TreeNode *last = NULL, *cur; 6 while(!s.empty()){ 7 cur = s.top(); 8 s.pop(); 9 if(last) 10 last->right = cur; 11 if(cur->right) 12 s.push(cur->right); 13 if(cur->left) 14 s.push(cur->left); 15 cur->left = NULL; 16 last = cur; 17 } 18 last->right = NULL; 19 } Populating Next Right Pointers in Each Node Given a binary tree: 1 struct TreeLinkNode { 2 TreeLinkNode * left;

47 2.4 其他 38 3 TreeLinkNode * right; 4 TreeLinkNode * next; 5 } Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL. Note: You may only use constant extra space. You may assume that it is a perfect binary tree (ie, all leaves are at the same level, and every parent has two children). (leetcode 116) 其实就是一个很简单的 BFS 过程. 迭代 : 时间复杂度 O(n), 空间复杂度 O(1) 因为是满二叉树, 所以每次在上一层建立这一层的 next, 然后再到这一层来, 这样就不需要队列, 使用常数的空间复杂度. 1 void connect(treelinknode *root) { 2 if(!root) return; 3 TreeLinkNode *cur = root, *next; 4 cur->next = NULL; 5 while(cur->left){ 6 next = cur->left; 7 while(cur){ 8 cur->left->next = cur->right; 9 cur->right->next = cur->next? cur->next->left : NULL; 10 cur = cur->next; 11 } 12 cur = next; 13 } 14 } Populating Next Right Pointers in Each Node II Follow up for problem Populating Next Right Pointers in Each Node.What if the given tree could be any binary tree? Would your previous solution still work? Note: You may only use constant extra space. (leetcode 117) 迭代 : 时间复杂度 O(n), 空间复杂度 O(1) 这里其实和上一题一样, 只不过多了一些判断条件. 1 void connect(treelinknode *root) { 2 if(!root) return; 3 TreeLinkNode *cur = root, *next, *last; 4 cur->next = NULL; 5 do{ 6 next = NULL; 7 while(cur){ 8 if(cur->left){ 9 if(next){ 10 last->next = cur->left; 11 last = last->next; 12 } 13 else{ 14 last = cur->left; 15 next = last; 16 } 17 }

48 2.4 其他 if(cur->right){ 19 if(next){ 20 last->next = cur->right; 21 last = last->next; 22 } 23 else{ 24 last = cur->left; 25 next = last; 26 } 27 } 28 cur = cur->next; 29 } 30 last->next = NULL; 31 cur = next; 32 }while(cur); 33 } Binary Search Tree Iterator Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the root node of a BST. Calling next() will return the next smallest number in the BST. (leetcode 173) Note: next() and hasnext() should run in average O(1) time and uses O(h) memory, where h is the height of the tree. Stack : 时间复杂度 O(1), 空间复杂度 O(lgn) 这里使用一个栈来保存上级节点, 每个 next 和 hasnext 的操作平均时间复杂度是 O(1) 1 class BSTIterator { 2 private: 3 TreeNode* root; 4 stack<treenode*> path; 5 public: 6 BSTIterator(TreeNode *root) : root(root){ 7 TreeNode* iter = root; 8 while(iter){ 9 path.push(iter); 10 iter = iter->left; 11 } 12 } whether we have a next smallest number */ 15 bool hasnext() { 16 return!path.empty(); 17 } the next smallest number */ 20 int next() { 21 TreeNode *cur = path.top(); 22 TreeNode *iter = cur->right; 23 path.pop(); 24 while(iter){

49 2.4 其他 path.push(iter); 26 iter = iter->left; 27 } 28 return cur->val; 29 } 30 };

50 第 3 章字符串 3.1 库函数 字符串的库函数有很多, 虽然大多数算法上都不是很难, 但是需要考虑很多细节问题, 所以需要研究一下. 关于字符串库函数需要注意的问题大致可以总结为以下几点 : 输入参数是什么类型, 该不该是 const 修饰 输入的指针是否为 NULL 字符串处理完毕后, 新产生的字符串末尾是否要加 \0 要不要返回值, 该返回什么类型的 ( 对于要返回值的是为了实现链式操作 ) 还有一些我觉得可能需要考虑的 : 有没有地址重叠 src 和 dest 地址一样 需不需要优化做法 strlen 求出字符串的长度. Care : 时间复杂度 O(n), 空间复杂度 O(1) 考虑输入指针不为空, 输入参数使用 const char 1 int strlen(const char *str){ 2 int len = 0; 3 assert(str); // 判断不为 NULL 4 while(*str++!= \0 ){ 5 len ++; 6 } 7 return len; 8 } strcat 将一个字符串连接到另一个字符串后面 Care : 时间复杂度 O(n), 空间复杂度 O(1) 输入参数 const 性和非 NULL, 尾赋 \0, 有返回值 41

51 3.1 库函数 42 1 char *strcat(char *strdest, const char *strscr){ 2 assert(strdest && strscr); 3 if(!strscr) return strdest; 4 char* p = strdest; 5 while(*p){ 6 p++; 7 } 8 while(*strscr){ 9 *p++ = *strscr++; 10 } 11 *p = \0 ; 12 return strdest; 13 } strcmp 比较两个字符串是否完全相同. Care : 时间复杂度 O(n), 空间复杂度 O(1) 输入参数 const 性和非 NULL, 注意如何写的简洁. 1 int strcmp(const char *str1,const char *str2){ 2 if(str1 == str2) return 0; 3 assert(str1 && str2); 4 // 这样写很简洁 5 while(*str1 && *str2 && (*str1 == *str2)){ 6 str1 ++; 7 str2 ++; 8 } 9 return (*str1) - (*str2); 10 } strcpy 将源字符串完全拷贝给目的字符串. Care : 时间复杂度 O(n), 空间复杂度 O(1) 考虑输入参数的 const 性, 输入参数是否合法, 返回值, 最后位置为 \0 1 char *strcpy(char *dest,const char *src){ 2 if(dest == src) return dest; 3 assert(dest && src); // 输入参数不为 NULL 4 char* address = dest; 5 int lens = strlen(src); 6 int lend = strlen(dest); 7 if(src + lens > dest){ // 从后往前拷贝 8 dest[lens] = \0 ; // 当 lens < 就很重要 lend 9 for(int i = lens - 1; i >= 0; i--){ 10 dest[i] = src[i]; 11 } 12 }else{ // 从前往后拷贝 13 for(int i = 0; i < lens; i++){ 14 dest[i] = src[i]; 15 } 16 dest[lens] = \0 ; // 当 lens < 就很重要 lend 17 }

52 3.1 库函数 return address; // 注意此函数有返回值 19 } 至于 src 和 dest 地址重叠问题,libc 里面并没有考虑, 我觉得考虑一下还是很好的 strncpy 将源字符串拷贝前 n 个字符给目的字符串. Care : 时间复杂度 O(n), 空间复杂度 O(1) 注意事项同 strcpy 1 char *strncpy(char *dest,const char *src, int n){ 2 assert(dest && src); 3 char *address = dest; 4 int lens = strlen(src); 5 int lend = strlen(dest); 6 if(src + lens > dest && src + n > dest){// 从后往前拷贝 7 dest[min(lens, n)] = \0 ; 8 for(int i = min(lens, n) - 1; i >= 0; i--){ 9 dest[i] = src[i]; 10 } 11 }else{ // 从前往后拷贝 12 for(int i = 0; i < lens && i < n; i++){ 13 dest[i] = src[i]; 14 } 15 dest[min(lens, n)] = \0 ; 16 } 17 return address; 18 } 注意不能仅仅因为 src == dest 就放弃拷贝 memcpy 将源地址开始的连续 n 个字节大小空间拷贝给给目的地址. Care : 时间复杂度 O(n), 空间复杂度 O(1) 注意输入参数是 void* 和 const void*, 输出参数是 void*, 要保证输出参数非 NULL 1 void *memcpy(void *dest, const void *src, size_t count){ 2 if(dest == src) return dest; 3 assert(src && dest); 4 if(!src!dest) return NULL; 5 unsigned char *d = (unsigned char*)dest, *s = (unsigned char*)src; 6 while(count--){ 7 *d++ = *s++; 8 } 9 // 不需要设置因为是内存拷贝 \0, 10 return dest; 11 } libc 使用了 page copy, unsigned long copy 做到快速拷贝

53 3.2 字符串经典问题 字符串经典问题 字符串的经典问题有很多, 虽然很多解法都属于算法范畴, 诸如动态规划等, 这里还是归结为字符串的问题 strstr Implement strstr(). Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. (leetcode 28) KMP : 时间复杂度 O(n), 空间复杂度 O(m) strstr 属于字符串的库函数, 放在这里讲完全是因为它是字符串问题中最有名的之一, 其解法多样, 这里推荐 KMP 解法, 关于此解法的介绍可以参考我们 ThreeCobblers 主页上 zhuoyuan 的博文 1 void gen_next(const char *p) { 2 next[0] = -1; 3 int i = 0; 4 int j = -1; 5 int lp = strlen(p); 6 while(i < lp) 7 if(j == -1 p[i] == p[j]) i++, j++, next[i] = j; 8 else j = next[j]; 9 } 10 int kmp(const char *s, const char *p) { 11 gen_next(p); 12 int ls = strlen(s); 13 int lp = strlen(p); 14 int i = -1; 15 int j = -1; 16 while(i < ls && j < lp) 17 if(j == -1 s[i] == p[j]) i++, j++; 18 else j = next[j]; 19 if(j == lp) return i - lp; 20 return -1; 21 } 这段代码水很深, 写的非常老道, 需要好好揣测才可以明白, 关于求 next 数组问题可以看上面说的那篇博客的介绍 atoi Implement atoi to convert a string to an integer. (leetcode 8) Note: The function first discards as many whitespace characters as necessary until the first nonwhitespace character is found. Then, starting from this character, takes an optional initial plus or minus sign followed by as many numerical digits as possible, and interprets them as a numerical value. The string can contain additional characters after those that form the integral number, which are ignored and have no effect on the behavior of this function. If the first sequence of non-whitespace characters in str is not a valid integral number, or if no such sequence exists because either str is empty or it contains only whitespace characters, no conversion is performed. If no valid conversion could be performed, a zero value is returned. If the correct value is out of the range of representable values, INT MAX ( ) or INT MIN ( ) is returned.

54 3.2 字符串经典问题 45 Care : 时间复杂度 O(n), 空间复杂度 O(1) 这个问题主要要注意两个方面, 一个是空格和非法字符, 一个是溢出, 空格和非法字符都很好处理, 溢出的处理方式就值得去研究, 在 C++ 中 INT MAX 的绝对值比 INT MIN 小 1, 所以负数个数比正数个数多一个, 你可以都转化位负数来判断溢出. 当然, 对付溢出还有一种偷懒的方法就是使用 double 类型取存储数据. 1 int atoi(const char *str){ 2 int sum = 0; // 存负值. 3 bool isminus = false; 4 const char* p = str; 5 if(!p) return 0; 6 while(*p == && *p!= \0 ) p++; // 空格 7 if(*p == \0 ) return 0; 8 if(*p == - ){ 9 isminus = true; 10 p++; 11 }else{ 12 if(*p == + ) p++; 13 } 14 while(*p!= \0 ){ 15 int cur = *p ; 16 if (!(cur >= 0 && cur <= 9)) break; // 非法字符 17 if(isminus && sum == INT_MIN/10 && cur > INT_MAX%10 + 1){ 18 return INT_MIN; 19 } 20 if(isminus && sum < INT_MIN/10){ 21 return INT_MIN; 22 } 23 if(!isminus && sum == -INT_MAX/10 && cur > INT_MAX%10){ 24 return INT_MAX; 25 } 26 if(!isminus && sum < -INT_MAX/10){ 27 return INT_MAX; 28 } 29 sum = sum*10 - cur; 30 } 31 if(!isminus) return -sum; 32 return sum; 33 } Reverse Words in a String Given an input string, reverse the string word by word.(leetcode 151) 举例 : Given s = the sky is blue, return blue is sky the. 反转 : 时间复杂度 O(n), 空间复杂度 O(n) 这也是一道经典的字符串题目, 题目的要求可以通过先把整个字符串反转一下, 然后从前到后 spilit, 每 spilit 一个词就反转这个词然后添加到后面, 即全局反转和逐个反转. 1 void reversewords(string &s) { 2 reverse(s.begin(), s.end()); 3 int i = 0, last; 4 string str, word; 5 while(i < s.length()){

55 3.2 字符串经典问题 46 6 while(i < s.length() && s[i] == ) i++; 7 if(i == s.length()) break; 8 last = i; 9 while(i < s.length() && s[i]!= ) i++; 10 word = s.substr(last, i - last); 11 reverse(word.begin(), word.end()); 12 str = str + word + " "; 13 } 14 if(!str.empty()) 15 s.assign(str.begin(), str.end() - 1); 16 else 17 s = str; 18 } Valid Palindrome Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.(leetcode 125) 举例 : A man, a plan, a canal: Panama is a palindrome. race a car is not a palindrome. Care : 时间复杂度 O(n), 空间复杂度 O(1) 1 bool isalpha(char c){ 2 if(c <= Z && c >= A ) return true; 3 if(c <= z && c >= a ) return true; 4 if(c <= 9 && c >= 0 ) return true; 5 return false; 6 } 7 8 bool ispalindrome( string s) { 9 int len = s.length(); 10 int start = 0, end = len - 1; 11 while(start < end){ 12 while(start < end &&!isalpha(s[start])) start++; 13 if(start == end) return true; 14 while(end > start &&!isalpha(s[end])) end--; 15 if(s[start]!= s[end] && abs(s[start] - s[end])!= abs( a - A )) return false; 16 start ++; 17 end--; 18 } 19 return true; 20 } LongestPalindrome Longest Palindromic Substring. (leetcode 5) Manacher : 时间复杂度 O(n), 空间复杂度 O(n) 这里介绍的是 Manacher 算法, 关于此算法的思想和证明可以参考我们 ThreeCobblers 主页上 sosohu 的博文 1 string longestpalindrome( string const& s) { 2 int n = s.length(); 3 if(n == 0) return "";

56 3.2 字符串经典问题 47 4 string str = "#"; 5 int count = 0; 6 for(int i = 0; i < n; i++){ 7 str += s[i]; 8 str += # ; 9 } 10 int id = 0, mx = 0; 11 vector<int> p(2*n+1, 0); 12 p[0] = 1; 13 for(int i = 1; i < 2*n + 1; i++){ 14 int j = 2*id - i; 15 p[i] = mx > i? min(p[j], mx - i) : 1; 16 while(i + p[i] < 2*n + 1 && i - p[i] >= 0){ 17 if(str[i + p[i]]!= str[i - p[i]]) break; 18 p[i]++; 19 } 20 if(i + p[i] > mx){ 21 mx = i+ p[i]; 22 id = i; 23 } 24 } 25 int max = INT_MIN; 26 int pos = 0; 27 for(int i = 0; i < 2*n + 1; i++){ 28 if(max < p[i]){ 29 max = p[i]; 30 pos = i; 31 } 32 } 33 int index, len; 34 len = (max - 1); 35 index = pos/2 - len/2; 36 return s.substr(index, len); 37 } Longest Substring Without Repeating Characters Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for abcabcbb is abc, which the length is 3. For bbbbb the longest substring is b, with the length of 1. (leetcode 3) DP : 时间复杂度 O(n), 空间复杂度 O(1) 这道题其实和最大连续和问题是一个类型的问题, 都是属于一维的动态规划, 说它是动态规划是因为父问题可以通过子问题来构造, 我们是 d[i] 表示字符串 s[1...i] 中的最长无重复串的长度,c[i] 表示 s[1...i] 中以 s[i] 为结尾的最长无重复子串的长度, 例如 s[1...5] = abcdb, 那么 d[5] = 4, c[5] = 3. 所以我们有递推关系 : d[i+1] = max(d[i],s i+1 s[i c[i]+1...i])?d[i] : c[i]+1) 根据这个递推关系式, 我们可以扫描字符串, 边扫描边统计 d[i] 和更新 c[i], 最终求得答案. 1 i n t lengthoflongestsubstring( string s) { 2 vector<bool> appear(256, false); 3 int ret = 0, con = 0; 4 for(int i = 0; i < s.length(); i++){ 5 if(!appear[s[i]]){

57 3.2 字符串经典问题 48 6 con ++; 7 appear[s[i]] = true; 8 ret = max(ret, con); 9 }else{ 10 for(int j = i - con; j < i && s[j]!= s[i]; j++){ 11 appear[s[j]] = false; 12 con--; 13 } 14 } 15 } 16 return ret; 17 } 这类问题很常见, 像最大连续和, 最长递增子串等等, 它们的共同点就是连续而且可以很方便的使用新加入的元素来更新最长靠右连续最优解 ( 本题的 c[i]), 父问题可以很容易的通过子问题求出 Anagrams Given an array of strings, return all groups of strings that are anagrams. Note: All inputs will be in lower-case. (leetcode 49) sort : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 变位词是字符串的一种常见问题, 很多时候都是先把一群互为变位词的词选出一个为它们的索引词 (key), 然后就可以把它们放在一起存储. 这道题如果不是先这样, 而是一个一个查找那么是非常低效的. 1 vector<string> anagrams(vector<string> &strs) { 2 int size = strs.size(); 3 unordered_map<string, vector<string> > table; 4 for(int i = 0; i < size; i++){ 5 string index = strs[i]; 6 sort(index.begin(), index.end()); 7 table[index].push_back(strs[i]); 8 } 9 vector<string> ret; 10 for(unordered_map<string, vector<string> >::iterator iter = table. begin(); 11 iter!= table.end(); iter++){ 12 if(iter->second.size() > 1){ 13 ret.insert(ret.end(), iter->second.begin(), iter->second.end ()); 14 } 15 } 16 return ret; 17 } Valid Number Validate if a given string is numeric. 举例 0 is true 0.1 is true abc is false 1 a is false 2e10 is true NFA : 时间复杂度 O(n), 空间复杂度 O(1) 这种问题最优美的解法就是先写出正则表达式, 然后根据正则表达式画出 NFA, 然后根据 NFA 的

58 3.2 字符串经典问题 49 状态转移写出代码. 正则表达式 : s (+ )?((d +.?) (.d))d (e(+ )?d + )? s 为空格,d 为数字 画出状态转移图如下 : s.. d e +/ /- d d d. d s d d s s 2 8 s 7 1 enum lexical{ valid, space, sign, number, dot, e }; 2 d 3 int istype(const char c){ 4 switch(c){ 5 case : return 1; 6 case + : ; 7 case - : return 2; 8 case. : return 4; 9 case e : return 5; 10 default: if(c <= 9 && c >= 0 ) return 3; 11 else return 0; 12 } 13 } bool isnumber(const char *s) { 16 if(!s) return false; 17 // 状态转移矩阵, 表示非法 int map[9][6] = { 19-1, 0, 1, 2, 3, -1, // 状态的转移 , -1, -1, 2, 3, -1, 21-1, 8, -1, 2, 4, 5, 22-1, -1, -1, 4, -1, -1, 23-1, 8, -1, 4, -1, 5, 24-1, -1, 6, 7, -1, -1, 25-1, -1, -1, 7, -1, -1, 26-1, 8, -1, 7, -1, -1, 27-1, 8, -1, -1, -1, }; 29 int state = 0; 30 while(*s){ 31 state = map[state][istype(*s)]; 32 if(state == -1) return false; 33 s++; 34 } 35 return state == 2 state == 4 state == 7 state == 8; 36 } 知道 NFA 转移图后想到像上面这些编写代码也是蛮难的, 这样编写的好处是可以灵活的改变 NFA, 不会对代码构成很大影响, 值得学习 Regular Expression Matching Implement regular expression matching with support for. and *.

59 3.2 字符串经典问题 50. Matches any single character. * Matches zero or more of the preceding element. The matching should cover the entire input string (not partial). (leetcode 10) 举例 : ismatch( aa, a ) false ismatch( aa, aa ) true ismatch( aaa, aa ) false ismatch( aa, a* ) true ismatch( aa,.* ) true ismatch( ab,.* ) true ismatch( aab, c*a*b ) true DP : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 本题使用动态规划求解, 设 ismatch[i][j] 表示 s[1...i] 与 p[1...j] 是否匹配递推关系是如下 : ism atch[i][j] = 1 i = 0,j = 0 0 i 0,j = 0 ismatch[i 1][j 1] && (s i 1 == p j 1 p j 1 ==. ) p j ismatch[i][j] = ismatch[i][j 2] ismatch[i 1][j] p j =,p j 1 =. ismatch[i][j 1] p j =,p j 1 = ismatch[i][j 2] (s i 1 == p j 1 && ismatch[i 1][j]) other 1 bool ismatch(const char *s, const char *p) { 2 int ls = strlen(s); 3 int lp = strlen(p); 4 vector<vector<bool> > ismatch(ls+1, vector<bool>(lp+1, false)); 5 ismatch[0][0] = true; 6 int di, dj; 7 for(int i = 0; i < ls + 1; i++){ // 起始坐标 8 di = i - 1; 9 for(int j = 1; j < lp + 1; j++){ // 起始坐标 10 dj = j - 1; 11 if(p[dj]!= * ){ 12 ismatch[i][j] = di!= -1 && ismatch[i-1][j-1] && (s[ di] == p[dj] p[dj] ==. ); 13 }else{ 14 if(dj == 0) { ismatch[i][j] = false; continue; } 15 if(p[dj-1] ==. ){ 16 ismatch[i][j] = ismatch[i][j-2] (di!= -1 && ismatch[i-1][j]); 17 }else{ 18 if(p[dj-1] == * ) ismatch[i][j] = ismatch[i][j -1]; 19 else ismatch[i][j] = ismatch[i][j-2] (di!= -1 && s[di] == p[dj-1] && ismatch[i-1][j]); 20 } 21 } 22 } 23 } 24 return ismatch[ls][lp]; 25 }

60 3.2 字符串经典问题 Wildcard Matching Implement wildcard pattern matching with support for? and *.? Matches any single character. * Matches any sequence of characters (including the empty sequence). The matching should cover the entire input string (not partial). (leetcode 44) 举例 : ismatch( aa, a ) false ismatch( aa, aa ) true ismatch( aaa, aa ) false ismatch( aa, * ) true ismatch( aa, a* ) true ismatch( ab,?* ) true ismatch( aab, c*a*b ) false 迭代 : 时间复杂度 O(n*m), 空间复杂度 O(1) 这道题其实是可以套用 Regular Expression Matching 的解法, 使用动态规划来计算, 但是那样在 leetcode 上会超时, 具体原因我还不清楚. 本题的解法其实也很明了, 唯一需要注意的就是每次我们遇到 * 时候都会记下此次 * 的位置以及主串位置, 然后一次一次的试探 * 是否要生成字母, 试探不成功就回到刚才我们记下的状态然后试探下一个状态, 但是, 当我们遇到下一个 * 时候, 就可以更新这个记录点了, 因为都已经到这个 * 了, 上个 * 走到这个 * 走的路肯定是对的, 即使不太对, 也可以通过这个 * 不断生成字母来弥补, 这就是需要注意的地方, 不是很好理解, 需要仔细琢磨. 1 bool ismatch(const char *s, const char *p){ 2 int ls = strlen(s); 3 int lp = strlen(p); 4 const char *ps = s, *pp = p, *lasts = NULL, *lastp = NULL; 5 if(!s!p) return false; 6 while(*s){ 7 switch(*p){ 8 case? : s++; p++; break; 9 case * : while(*(p+1) && *(p+1) == * ) p++; 10 lasts = s; lastp = p; p++; break; 11 default: if(*s == *p){ s++; p++;} 12 else{ 13 if(lasts){ 14 s = ++lasts; 15 p = lastp + 1; 16 }else{ 17 return false; 18 } 19 } 20 } 21 } 22 if(*p == \0 && *s == \0 ) return true; 23 if ((*p) == \0 && *(p-1)!= * ) return false; 24 while(*p && *p == * ){ 25 p++; 26 } 27 if(*p!= \0 ) return false; 28 return true; 29 }

61 3.2 字符串经典问题 Edit Distance Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.) You have the following 3 operations permitted on a word: a) Insert a character b) Delete a character c) Replace a character (leetcode 71) DP : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 两个字符串的编辑距离是一个很经典的问题, 在信息检索领域有着重要的作用, 它的解法需要使用动态规划来求解 我们设 d i,j 表示字符串 s[1...i] 和 p[1...j] 的编辑距离,w i,w d,w r 分别表示插入, 删除和替换的操作代价 ( 在本题都为 1), 那么我们可以得到递推关系式 : j i = 0 d i,j = i j = 0 min{d i 1,j +w i,d i,j 1 +w d,d i 1,j 1 +(s i == p j?0 : w r )} other 1 int mindistance(string word1, string word2) { 2 int l1 = word1.length(), l2 = word2.length(); 3 vector<vector<int> > d(l1+1, vector<int>(l2+1, 0)); 4 for(int i = 0; i < l1 + 1; i++) d[i][0] = i; 5 for(int j = 0; j < l2 + 1; j++) d[0][j] = j; 6 7 for(int i = 1; i < l1 + 1; i++){ 8 for(int j = 1; j < l2 + 1; j++){ 9 if(word1[i-1] == word2[j-1]) d[i][j] = d[i-1][j-1]; 10 else d[i][j] = d[i-1][j-1] + 1; 11 d[i][j] = min(d[i][j], d[i-1][j] + 1); 12 d[i][j] = min(d[i][j], d[i][j-1] + 1); 13 } 14 } return d[l1][l2]; 17 } Minimum Window Substring Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).(leetcode 76) 举例 : S = ADOBECODEBANC T = ABC Minimum window is BANC. Note: If there is no such window in S that covers all characters in T, return the emtpy string. If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

62 3.2 字符串经典问题 53 Hash : 时间复杂度 O(n), 空间复杂度 O(1) 这道题很容易想到用哈希表来求解, 但是对于这个存在重复多的 key, 我一开始想到的是哈希的 value 存储一个 list 来放这些重复元素, 后来发现对于这道题, 其实你只要记录次数就行了, 每次准备向右移动窗口时候, 只要要被移掉的字符它的 hash 值大于原数目就说明是多的, 可以移除的, 所以这题就简单开朗很多了. 1 string minwindow(string S, string T) { 2 vector<int> table(256, 0); 3 vector<int> apper(256, 0); 4 for(int i = 0; i < T.length(); i++) table[t[i]]++; 5 int len = INT_MAX, num = 0, last = 0, pos = 0; 6 for(int i = 0; i < S.length(); i++){ 7 apper[s[i]]++; 8 if(apper[s[i]] <= table[s[i]]) num++; 9 while(apper[s[last]] > table[s[last]]){ 10 apper[s[last]]--; 11 last ++; 12 } 13 if(num == T.length() && len > i - last + 1){ 14 len = i - last + 1; 15 pos = last; 16 } 17 } 18 return num == T.length()? S.substr(pos, len) : ""; 19 } 在字符串很多问题中都需要用到哈希表, 特别是一个主串一个副串, 在主串找副串的元素这类问题.

63 3.3 字符串其他问题 字符串其他问题 Integer to Roman Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 to (leetcode 12) Trick : 时间复杂度 O(1), 空间复杂度 O(1) 这道题其实比较简单, 但是怎么写的优雅是一个很难的事, 下面的代码就很 trick, 需要记住. 1 string inttoroman(int num) { 2 string ret; 3 int stand[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; 4 string symbol[] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", " X", "IX", "V", "IV", "I"}; 5 for(int i = 0; num > 0; i++){ 6 int count = num / stand[i]; 7 num = num % stand[i]; 8 for(; count > 0; count--) ret += symbol[i]; 9 } 10 return ret; 11 } 写的实在是太简洁, 太美, 太富有穿透力了, 应该当做教科书一样背下来 Roman to Integer Given a roman numeral, convert it to an integer.input is guaranteed to be within the range from 1 to (leetcode 13) Care : 时间复杂度 O(1), 空间复杂度 O(1) 同样, 怎么写的优雅是这题的最难的地方. 1 int mapnum(char c){ 2 switch(c){ 3 case I : return 1; 4 case V : return 5; 5 case X : return 10; 6 case L : return 50; 7 case C : return 100; 8 case D : return 500; 9 case M : return 1000; 10 default: return 0; 11 } 12 } int romantoint(string s) { 15 int len = s.length(); 16 int sum = 0; 17 for(int i = 0; i < len; i++){ 18 if(i < len - 1 && mapnum(s[i]) < mapnum(s[i+1])) 19 sum -= mapnum(s[i]); 20 else sum += mapnum(s[i]); 21 } 22 return sum; 23 }

64 3.3 字符串其他问题 Longest Common Prefix Write a function to find the longest common prefix string amongst an array of strings. (leetcode 14) Care : 时间复杂度 O(n*m), 空间复杂度 O(1) 这道题比较简单, 需要注意的还是怎么写的简洁优雅. 1 string longestcommonprefix(vector<string> &strs) { 2 string ret; 3 if(strs.size() == 0) return ret; 4 int len = INT_MAX; 5 for(int i = 0; i < strs.size(); i++) 6 if(len > strs[i].length()) len = strs[i].length(); 7 for(int j = 0; j < len; j++){ 8 for(int i = 1; i < strs.size(); i++) 9 if(strs[i][j]!= strs[i-1][j]) return ret; 10 ret.push_back(strs[0][j]); 11 } 12 return ret; 13 } Count and Say The count-and-say sequence is the sequence of integers beginning as follows: 1, 11, 21, 1211, ,... 1 is read off as one 1 or is read off as two 1s or is read off as one 2, then one 1 or Given an integer n, generate the nth sequence.(leetcode 38) NoteThe sequence of integers will be represented as a string. 递推 : 时间复杂度 O(n*m), 空间复杂度 O(m) 根据递推关系一步步的推. 1 string parse(string& str){ 2 string ret; 3 int last = 0, len = str.length(); 4 for(int i = 0; i < len; i++){ 5 last = i; 6 while(i < len - 1 && str[i] == str[i+1]) i++; 7 ret.push_back(i - last ); 8 ret.push_back(str[last]); 9 } 10 return ret; 11 } string countandsay(int n) { 14 string last = "1", cur; 15 if(n < 1) return cur; 16 while(n-- > 1){ 17 last = cur = parse(last); 18 } 19 return last; 20 }

65 3.3 字符串其他问题 Add Binary Given two binary strings, return their sum (also a binary string).(leetcode 67) 举例 : a = 11 b = 1 Return 100. 迭代 : 时间复杂度 O(n), 空间复杂度 O(1) 和链表的那个完全类似, 解法也是基本一致, 优雅的代码. 1 string addbinary(string a, string b) { 2 int la = a.length(), lb = b.length(); 3 int carry = 0, sum = 0; 4 string ret; 5 while(la lb carry){ 6 sum = carry + (la > 0? a[la-1] - 0 : 0) + (lb > 0? b[lb-1] - 0 : 0); 7 ret.push_back(sum%2 + 0 ); 8 carry = sum/2; 9 if(la) la--; 10 if(lb) lb--; 11 } 12 reverse(ret.begin(), ret.end()); 13 return ret; 14 } ZigZag Conversion The string PAYPALISHIRING is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility) P A H N A P L S I I G Y I R And then read line by line: PAHNAPLSIIGYIR Write the code that will take a string and make this conversion given a number of rows: string convert(string text, int nrows); convert( PAYPALISHIRING, 3) should return PAHNAPLSIIGYIR. (leetcode 6) 数学推导 : 时间复杂度 O(n), 空间复杂度 O(1) 这题就是简单的推导一下每一行相邻两个位置的间隔, 然后编写代码. 1 string convert(string s, int nrows) { 2 if(nrows == 1) return s; 3 int len = s.length(); 4 string ret; 5 for(int i = 0; i < nrows; i++){ 6 int start = i, gap = nrows - i - 1; 7 while(start < len){

66 3.3 字符串其他问题 57 8 ret.push_back(s[start]); 9 if(!gap) gap = nrows gap; 10 start = start + 2*gap; 11 gap = nrows gap; 12 } 13 } 14 return ret; 15 } 这段代码写的很简洁, 每一行都有一个自己的 gap 表示这一行相邻两个位置的间隔, 除了第一行和最后一行 gap 值一直都会反转, 与实际的推导结果一致 Length of Last Word Given a string s consists of upper/lower-case alphabets and empty space characters, return the length of last word in the string. If the last word does not exist, return 0.(leetcode 58) 举例 Given s = Hello World, return 5. Note A word is defined as a character sequence consists of non-space characters only. Care : 时间复杂度 O(n), 空间复杂度 O(1) 这题其实就是套用 spilit 操作, 过程是一模一样的. 1 int lengthoflastword(const char *s) { 2 int len = 0; 3 if(!s) return len; 4 while(*s){ 5 while(*s && *s == ) s++; 6 if(*s == \0 ) return len; 7 len = 0; 8 while(*s && *s!= ) {s++; len++;} 9 } 10 return len; 11 } Text Justification Given an array of words and a length L, format the text such that each line has exactly L characters and is fully (left and right) justified. You should pack your words in a greedy approach; that is, pack as many words as you can in each line. Pad extra spaces when necessary so that each line has exactly L characters. Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a line do not divide evenly between words, the empty slots on the left will be assigned more spaces than the slots on the right. For the last line of text, it should be left justified and no extra space is inserted between words.(leetcode 68) Note: Each word is guaranteed not to exceed L in length. A line other than the last line might contain only one word. What should you do in this case? In this case, that line should be left-justified.

67 3.3 字符串其他问题 58 Care : 时间复杂度 O(n), 空间复杂度 O(L) 这题比较繁琐, 需要考虑很多边界情况, 弄清楚题目的所有边界情况很重要, 代码写的也是根琐碎. 1 vector<string> fulljustify(vector<string> &words, int L) { 2 vector<string> ret; 3 if(l < 1!words.size()){ 4 string str(l, ); 5 ret.push_back(str); 6 return ret; 7 } 8 int last = 0, cur = 0, num = 0, left = 0; 9 while(cur < words.size()){ 10 int sum = 0; 11 last = cur; 12 while(cur < words.size() && sum + words[cur].length() <= L){ 13 sum = sum + words[cur].length() + 1; 14 cur ++; 15 } 16 num = cur - last; // 词数 17 sum = sum - num; // 个词总长 num 18 left = L - sum; // 余下空格长度 19 if(num == 1){// 一行只有一个词 20 string margin(left, ); 21 words[last] = words[last] + margin; 22 ret.push_back(words[last]); 23 continue; 24 } 25 if(cur == words.size()){// 最后一行 26 string line, s(1, ); 27 for(int i = 0; i < num - 1; i++){ 28 line = line + words[last++] + s; 29 } 30 line = line + words[last++]; 31 string margin(left - num + 1, ); 32 line = line + margin; 33 ret.push_back(line); 34 continue; 35 } 36 int gap = left / (num - 1); 37 int remain = left % (num - 1); 38 string line, s1(gap + (remain == 0? 0 : 1), ), s2(gap, ); 39 for(int i = 0; i < remain; i++){ 40 line = line + words[last++] + s1; 41 } 42 for(int i = remain; i < num - 1; i++){ 43 line = line + words[last++] + s2; 44 } 45 line += words[last]; 46 ret.push_back(line); 47 } 48 return ret; 49 } Compare Version Numbers Compare two version numbers version1 and version2.

68 3.3 字符串其他问题 59 If version1 version2 return 1, if version1 version2 return -1, otherwise return 0. You may assume that the version strings are non-empty and contain only digits and the. character. The. character does not represent a decimal point and is used to separate number sequences. For instance, 2.5 is not two and a half or half way to version three, it is the fifth secondlevel revision of the second first-level revision. (leetcode 165) 举例 : Here is an example of version numbers ordering: 0.1 < 1.1 < 1.2 < 13.37??? : 时间复杂度 O(n), 空间复杂度 O(1) 这题主要难度就是字符串的处理, 扫描出合法的数, 然后逐个比较. 1 int compareversion(string version1, string version2) { 2 int i = 0, j = 0, n1 = version1.size(), n2 = version2.size(); 3 if(n1 == 0 && n2 == 0) return 0; 4 if(n1 == 0 n2 == 0) 5 return n1 == 0? compareversion("0", version2) : compareversion( version1, "0"); 6 while(i < n1 && version1[i] ==. ) i++; 7 while(j < n2 && version2[j] ==. ) j++; 8 if(i == n1 && j == n2) return 0; 9 if(i == n1 j == n2) return i == n1? -1 : 1; 10 int ei = i, ej = j; 11 while(ei < n1 && version1[ei]!=. ) ei++; 12 while(ej < n2 && version2[ej]!=. ) ej++; 13 int v1 = atoi(version1.substr(i, ei - i).c_str()); 14 int v2 = atoi(version2.substr(j, ej - i).c_str()); 15 if(v1 == v2) 16 return compareversion(version1.substr(ei, n1 - ei), version2. substr(ej, n2 - ej)); 17 if(v1 < v2) return -1; 18 else return 1; 19 }

69 第 4 章数组 4.1 变长数组 C++ 中变长数组就是 vector, 关于它的实现可以参考我的博客. 60

70 4.2 经典问题 经典问题 Remove Duplicates from Sorted Array Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length. Do not allocate extra space for another array, you must do this in place with constant memory. (leetcode 26) 举例 : Given input array nums = [1,1,2], Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively. It doesn t matter what you leave beyond the new length. Two-Pointer : 时间复杂度 O(n), 空间复杂度 O(1) 维持两个指针 ( 以下代码中分别是 i 和 pos), 一个指向要处理的元素, 一个指向有效元素的存储位置 1 int removeduplicates(vector<int>& nums) { 2 int pos = 0; 3 for(int i = 0; i < nums.size(); i++){ 4 if (!(i < nums.size() - 1 && nums[i] == nums[i+1])) 5 nums[pos++] = nums[i]; 6 } 7 nums.resize(pos); 8 return pos; 9 } Remove Duplicates from Sorted Array II Follow up for Remove Duplicates : What if duplicates are allowed at most twice? (leetcode 80) 举例 : Given sorted array nums = [1,1,1,2,2,3], Your function should return length = 5, with the first five elements of nums being 1, 1, 2, 2 and 3. It doesn t matter what you leave beyond the new length. Two Pointer : 时间复杂度 O(n), 空间复杂度 O(1) 1 int removeduplicates(vector<int>& nums) { 2 int pos = 0; 3 for(int i = 0; i < nums.size(); i++){ 4 nums[pos++] = nums[i]; 5 if(i < nums.size() - 1 && nums[i] == nums[i+1]){ 6 while(i < nums.size() - 1 && nums[i] == nums[i+1]) 7 i++; 8 nums[pos++] = nums[i]; 9 } 10 }

71 4.2 经典问题 nums.resize(pos); 12 return pos; 13 } Remove Element Given an array and a value, remove all instances of that value in place and return the new length. The order of elements can be changed. It doesn t matter what you leave beyond the new length. (leetcode 27) Two-Pointer : 时间复杂度 O(n), 空间复杂度 O(1) 与 Remove Duplicate of Sort Array 类似 1 int removeelement(vector<int>& nums, int val) { 2 int pos = 0; 3 for(int i = 0; i < nums.size(); i++){ 4 if(nums[i]!= val) nums[pos++] = nums[i]; 5 } 6 nums.resize(pos); 7 return pos; 8 } First Missing Positive Given an unsorted integer array, find the first missing positive integer. Your algorithm should run in O(n) time and uses constant space. (leetcode 41) 举例 : Given [1,2,0] return 3, and [3,4, 1,1] return 2. : 时间复杂度, 空间复杂度 O 这题题目的要求其实就是最大的提示了, 即要求 O(n) 时间复杂度, 又要求 O(1) 空间复杂度, 那么思来想去也只有践踏原有数组这一个办法了, 至于如何践踏, 我们发现数组中元素的位置分别是 0,1,2,3,... 正好可以一一对应 1,2,3,4... 于是就可以设定对于元素放在对应位置 1 int firstmissingpositive(vector<int>& nums) { 2 int pos = 0; 3 while(pos < nums.size()){ 4 if(nums[pos] == pos + 1 nums[pos] > nums.size() 5 nums[pos] <= 0 nums[nums[pos] - 1] == nums[pos]) 6 pos ++; 7 else swap(nums[pos], nums[nums[pos] - 1]); 8 } 9 for(int i = 0; i < nums.size(); i++) 10 if(nums[i]!= i+1) return i + 1; 11 return nums.size() + 1; 12 }

72 4.2 经典问题 Rotate Image You are given an n x n 2D matrix representing an image. Rotate the image by 90 degrees (clockwise). (leetcode 48) Follow Up: Could you do this in-place? 翻转 : 时间复杂度 O(n 2 ), 空间复杂度 O(1) 这题是编程珠玑中经典问题, 可以采用对角翻转和折半翻转的组合完成旋转 1 void rotate(vector<vector<int> > &matrix) { 2 int n = matrix.size(); 3 for(int i = 0; i < n; i++){ 4 reverse(matrix[i].begin(), matrix[i].end()); 5 } 6 for(int i = 0; i < n; i++){ 7 for(int j = 0; j < n i; j++){ 8 swap(matrix[i][j], matrix[n-1-j][n-1-i]); 9 } 10 } 11 } Rotate Array Rotate an array of n elements to the right by k steps. (leetcode 189) 举例 : With n = 7 and k = 3, the array [1,2,3,4,5,6,7] is rotated to [5,6,7,1,2,3,4]. 翻转 : 时间复杂度 O(n), 空间复杂度 O(1) 这题是编程珠玑中的经典例题, 可以通过三次翻转实现 shift 动作 1 void rotate(vector<int>& nums, int k) { 2 if(nums.size() == 0 (k = k % nums.size()) == 0) 3 return; 4 reverse(nums.begin(), nums.end() - k); 5 reverse(nums.end() - k, nums.end()); 6 reverse(nums.begin(), nums.end()); 7 }

73 4.3 相关问题 相关问题 Spiral Matrix Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order. (leetcode 54) 举例 : Given the following matrix: [ [1,2,3], [4,5,6], [7,8,9] ] You should return [1,2,3,6,9,8,7,4,5].??? : 时间复杂度 O(n 2 ), 空间复杂度 O(1) 从外到内一环一环的处理, 需要注意一些边界条件 1 vector<int> spiralorder(vector<vector<int> > &matrix) { 2 vector<int> result; 3 int n = matrix.size(); 4 if(n == 0) return result; 5 int m = matrix[0].size(); 6 int magrin = 0; 7 while(m magrin >= magrin && n magrin >= magrin){ 8 for(int j = magrin; j <= m magrin; j++) 9 result.push_back(matrix[magrin][j]); 10 for(int i = magrin + 1; i < n magrin; i++) 11 result.push_back(matrix[i][m-1-magrin]); 12 if(n magrin!= magrin) 13 for(int j = m magrin; j >= magrin; j-- ) 14 result.push_back(matrix[n-1-magrin][j]); 15 if(m magrin!= magrin) 16 for(int i = n magrin - 1; i > magrin; i--) 17 result.push_back(matrix[i][magrin]); 18 magrin ++; 19 } 20 return result; 21 } Spiral Matrix II Given an integer n, generate a square matrix filled with elements from 1 to n 2 in spiral order. (leetcode 59) 举例 : Given n = 3, You should return the following matrix: [ [1,2,3],

74 4.3 相关问题 65 [8,9,4], [7,6,5] ]??? : 时间复杂度 O(n 2 ), 空间复杂度 O(1) 1 vector<vector<int> > generatematrix(int n) { 2 vector<vector<int> > matrix(n, vector<int>(n, 0)); 3 int pos = 1; 4 int magrin = 0; 5 while(n magrin >= magrin && n magrin >= magrin){ 6 for(int j = magrin; j <= n magrin; j++) 7 matrix[magrin][j] = pos++; 8 for(int i = magrin + 1; i < n magrin; i++) 9 matrix[i][n-1-magrin] = pos++; 10 if(n magrin!= magrin) 11 for(int j = n magrin; j >= magrin; j-- ) 12 matrix[n-1-magrin][j] = pos++; 13 if(n magrin!= magrin) 14 for(int i = n magrin - 1; i > magrin; i--) 15 matrix[i][magrin] = pos++; 16 magrin ++; 17 } 18 return matrix; 19 } Set Matrix Zeroes Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place. (leetcode 73) Follow Up: Did you use extra space? A straight forward solution using O(mn) space is probably a bad idea. A simple improvement uses O(m + n) space, but still not the best solution. Could you devise a constant space solution???? : 时间复杂度 O(n 2 ), 空间复杂度 O(1) 从上到下一行一行的处理, 如果上一个存在 0, 可以先保留上一行的现场, 然后根据上一行的原来值更新本行, 然后处理上一行. 唯一需要注意的就是, 如果本行出现新 0 需要更新该 0 所在列的上面所有行. 1 void setzeroes(vector<vector<int> > &matrix) { 2 int n = matrix.size(); 3 if(n == 0) return; 4 int m = matrix[0].size(); 5 bool lastzero = false; 6 for(int i = 0; i < n; i++){ 7 bool thiszero = false; 8 for(int j = 0; j < m; j++){ 9 if(matrix[i][j] == 0){ 10 int up = i - 1; 11 while(up >= 0){ 12 matrix[up][j] = 0; 13 up--; 14 }

75 4.3 相关问题 thiszero = true; 16 } 17 if(i > 0 && matrix[i-1][j] == 0) 18 matrix[i][j] = 0; 19 } 20 if(lastzero){ 21 for(int j = 0; j < m; j++) 22 matrix[i-1][j] = 0; 23 } 24 lastzero = thiszero; 25 } 26 if(lastzero){ 27 for(int j = 0; j < m; j++) 28 matrix[n-1][j] = 0; 29 } 30 } Pascal s Triangle Given numrows, generate the first numrows of Pascal s triangle. (leetcode 118) 举例 : Given numrows = 5, Return [ [1], [1,1], [1,2,1], [1,3,3,1], [1,4,6,4,1] ]??? : 时间复杂度 O(n 2 ), 空间复杂度 O(1) 1 vector<vector<int> > generate(int numrows) { 2 vector<vector<int> > result; 3 if(numrows == 0) return result; 4 result.push_back(vector<int >{1}); 5 for(int i = 1; i < numrows; i++){ 6 vector<int> cur; 7 cur.push_back(1); 8 for(int j = 0; j < result[i-1].size() - 1; j++) 9 cur.push_back(result[i-1][j] + result[i-1][j+1]); 10 cur.push_back(1); 11 result.push_back(cur); 12 } 13 return result; 14 } Pascal s Triangle II Given an index k, return the kth row of the Pascal s triangle. (leetcode 119)

76 4.3 相关问题 67 举例 : Given k = 3, Return [1,3,3,1]. Note: Could you optimize your algorithm to use only O(k) extra space???? : 时间复杂度 O(k 2 ), 空间复杂度 O(k) 这个空间复杂度可以优化, 因为每次我们只需要上一行就可以产生本行, 所有之前的行可以不存储 1 vector<int> getrow(int rowindex) { 2 rowindex ++; 3 if(rowindex <= 0) return vector<int>(); 4 vector<int> result{1}; 5 for(int i = 1; i < rowindex; i++){ 6 vector<int> cur; 7 cur.push_back(1); 8 for(int j = 0; j < result.size() - 1; j++) 9 cur.push_back(result[j] + result[j+1]); 10 cur.push_back(1); 11 result.swap(cur); 12 } 13 return result; 14 }

77 第 5 章栈和队列 5.1 基本概念 栈是一种非常常见的数据结构, 因为它具有先进后出的特点, 通常被用来模拟递归的实现. 68

78 5.2 栈的设计与实现 栈的设计与实现 在 STL 里面, 栈是一个适配器, 所谓适配器就是嫁接在其他基本容器之上的容器. 像 SGI-STL 里面栈默认就是内部通过 deque 来模拟实现, 当然我们也可以通过 vector, list 甚至 array 来包装成栈. TODO

79 5.3 相关问题 相关问题 Valid Parentheses Given a string containing just the characters (, ),,, [ and ], determine if the input string is valid. The brackets must close in the correct order, () and ()[]{} are all valid but (} and ([)] are not. (leetcode 20) 栈 : 时间复杂度 O(n), 空间复杂度 O(n) 括号匹配的原则是每个右括号都与最左边最近的左括号匹配, 所以这和栈的先进后出完全一致, 所以使用栈就非常方便. 1 bool isvalid(string s) { 2 // 0,1,2 represent (, [, { 3 stack<int> left; 4 for(int i = 0; i < s.size(); i++){ 5 switch(s[i]){ 6 case ( : left.push(0); break; 7 case [ : left.push(1); break; 8 case { : left.push(2); break; 9 case ) : if(left.empty() left.top()!= 0) 10 return false; left.pop(); break; 11 case ] : if(left.empty() left.top()!= 1) 12 return false; left.pop(); break; 13 case } : if(left.empty() left.top()!= 2) 14 return false; left.pop(); break; 15 default: return false; 16 } 17 } 18 return left.empty(); 19 } Trapping Rain Water Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining. (leetcode 42) 举例 : Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.

80 5.3 相关问题 71 栈 : 时间复杂度 O(n), 空间复杂度 O(n) 这道题可以这样想, 依次遍历 a 1,a 2,...,a n 如果 a i a i 1 那么加入到栈中 ; 反之, 进行弹栈处理, 因为 a i > a i 1 a i 2 所以可以计算 a i 1 处的积水, 依次类推. 1 int trap(vector<int>& height) { 2 if(height.size() <= 1) return 0; 3 stack<pair<int,int> > s; 4 s.push(make_pair(height[0], 0)); 5 int sum = 0; 6 pair<int,int> cur; 7 for(int i = 1; i < height.size(); i++){ 8 while(height[i] > s.top().first){ 9 cur = s.top(); 10 s.pop(); 11 if(!s.empty()) 12 sum += (min(height[i], s.top().first) - cur.first) 13 * (i - s.top().second - 1); 14 else break; 15 } 16 s.push(make_pair(height[i], i)); 17 } 18 return sum; 19 } DP : 时间复杂度 O(n), 空间复杂度 O(n) 其实栈的方法比较麻烦, 这里推荐一种更简单直接的做法, 我们来考虑每个元素 a i 它能盛放的水量 :V i, 我们可以轻易的知道 V i = min(left[i],right[i]), 其中 left[i],right[i] 分别是 a i 左右连续最大值. 那么, 我们可以通过动态规划计算 left i right i : { a 0 i = 0 left[i] = 最终 target = n 1 i=0 V i right[i] = max(a i,left[i 1]) other { a n 1 i = n 1 max(a i,right[i+1]) other 1 int trap(int A[], int n) { 2 vector<int> left(n, 0), right(n, 0); 3 left[0] = A[0]; 4 right[n-1] = A[n-1]; 5 for(int i = 1; i < n; i++){ 6 A[i] = A[i] < left[i-1]? left[i-1] : A[i]; 7 } 8 9 for(int i = n - 2; i >= 0; i--){ 10 A[i] = A[i] < right[i+1]? right[i+1] : A[i]; 11 } int sum = 0; 14 for(int i = 0; i < n; i++){ 15 min = left[i] < right[i]? left[i] : right[i]; 16 sum = sum + min - A[i]; 17 } 18 return sum; 19 }

81 5.3 相关问题 Largest Rectangle in Histogram Given n non-negative integers representing the histogram s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram. (leetcode 84) 举例 : Given height = [2,1,5,6,2,3], Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3]. The largest rectangle is shown in the shaded area, which has area = 10 unit. return 10. 栈 : 时间复杂度 O(n), 空间复杂度 O(n) 我们可以这样考虑这个问题, 我么依次扫描 a 1,a 2,...,a n 如果 a i a i 1 就将 a i 加入到栈中 ; 反之, 我们就弹栈处理 a i 1, 因为 a i 2 < a i 1 > a i 所以我们可以计算以 a i 1 为基准的 histogram 大小, 依次类推. 1 int largestrectanglearea(vector<int> &height) { 2 if(height.size() == 0) return 0; 3 if(height.size() == 1) return height[0];

82 5.3 相关问题 73 4 stack<pair<int, int> > s; 5 s.push(make_pair(height[0], 0)); 6 int result = INT_MIN; 7 for(int i = 1; i < height.size(); i++){ 8 while(height[i] < s.top().first){ 9 pair<int, int> cur = s.top(); 10 s.pop(); 11 if(!s.empty()){ 12 if(result < cur.first * (i - s.top().second - 1)) 13 result = cur.first * (i - s.top().second - 1); 14 }else{ 15 if(result < cur.first * i) 16 result = cur.first * i; 17 break; 18 } 19 } 20 s.push(make_pair(height[i], i)); 21 } 22 int index = s.top().second; 23 while(!s.empty()){ 24 pair<int, int> cur = s.top(); 25 s.pop(); 26 if(!s.empty()){ 27 if(result < cur.first * (index - s.top().second)) 28 result = cur.first * (index - s.top().second); 29 }else{ 30 if(result < cur.first * (index + 1)) 31 result = cur.first * (index + 1); 32 } 33 } 34 return result; 35 } DP : 时间复杂度 O(n), 空间复杂度 O(n) 同样类似前面的 Traip Water 问题, 分别计算 left[i]right[i] 即可 1 int largestrectanglearea_1st(vector<int> &height) { 2 int len = height.size(); 3 if(len == 0) return 0; 4 5 vector<int> left(len, 0), right(len, 0); 6 7 for(int i = 1; i < len; i++){ 8 for(int j = i - 1; j >=0; j--){ 9 if(height[i] <= height[j]){ 10 left[i] = left[i] + left[j] + 1; 11 j = j - left[j]; 12 }else{ 13 break; 14 } 15 } 16 } 17 for(int i = len - 2; i >= 0; i--){ 18 for(int j = i+1; j < len; j++){ 19 if(height[i] <= height[j]){ 20 right[i] = right[i] + right[j] + 1; 21 j = j + right[j]; 22 }else{

83 5.3 相关问题 break; 24 } 25 } 26 } 27 int data = 0, tmp; for(int i = 0; i < len; i++){ 30 tmp = height[i] * (left[i] + right[i] + 1); 31 if(data < tmp){ 32 data = tmp; 33 } 34 } 35 return data; 36 } Evaluate Reverse Polish Notation Evaluate the value of an arithmetic expression in Reverse Polish Notation. Valid operators are +, -, *, /. Each operand may be an integer or another expression. (leetcode 150) 举例 : [ 2, 1, +, 3, ] ((2+1) 3) > 9 [ 4, 13, 5, /, + ] (4+(13/5)) > 6 栈 : 时间复杂度 O(n), 空间复杂度 O(n) 逆波兰式是典型的栈式问题. 1 bool scannum(const string &str, int& num){ 2 if(str.size() == 1 && (str[0] == + str[0] == - 3 str[0] == * str[0] == / ) ) 4 return false; 5 bool ismunis = false; 6 if(str[0] == - ) ismunis = true; 7 num = 0; 8 for(int i = 0; i < str.size(); i++) 9 if(str[i]!= + && str[i]!= - ) 10 num = num*10 + str[i] - 0 ; 11 num = ismunis? -num : num; 12 return true; 13 } int evalrpn(vector<string>& tokens) { 16 int result = 0, num = 0; 17 if(tokens.size() == 0) return 0; 18 scannum(tokens[0], num); 19 if(tokens.size() <= 2) return num; 20 stack<int> s; 21 s.push(num); 22 scannum(tokens[1], num); 23 s.push(num); 24 for(int i = 2; i < tokens.size(); i++){ 25 if(!scannum(tokens[i], num)){ 26 int first = s.top(); s.pop(); 27 int second = s.top(); s.pop(); 28 switch(tokens[i][0]){

84 5.3 相关问题 case + : s.push(second + first); break; 30 case - : s.push(second - first); break; 31 case * : s.push(second * first); break; 32 case / : s.push(second / first); break; 33 } 34 }else{ 35 s.push(num); 36 } 37 } 38 result = s.top(); 39 return result; 40 } Min Stack Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. push(x) Push element x onto stack. pop() Removes the element on top of the stack. top() Get the top element. getmin() Retrieve the minimum element in the stack. (leetcode 155) 栈 : 时间复杂度 O(n), 空间复杂度 O(n) 这里采用经典的双栈法处理, 一个栈储存数据, 另一个栈储存最小值. 1 class MinStack { 2 3 public: 4 5 void push(int x) { 6 data.push(x); 7 if(track.empty() x <= track.top()) 8 track.push(x); 9 } void pop(){ 12 if(data.top() == track.top()) 13 track.pop(); 14 data.pop(); 15 } int top(){ 18 return data.top(); 19 } int getmin(){ 22 return track.top(); 23 } private: 26 stack<int> data; 27 stack<int> track; 28 };

85 第 6 章图 6.1 基本概念 图是数据结构中一个非常重要的部分 图的邻接矩阵表示 TODO 图的邻接表示 TODO 76

86 6.2 经典问题 经典问题 关于图的经典问题我在 github 上有过总结, 请戳这里

87 6.3 相关问题 相关问题 Clone Graph Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors. OJ s undirected graph serialization: Nodes are labeled uniquely. We use as a separator for each node, and, as a separator for node label and each neighbor of the node. (leetcode 133) 举例 : As an example, consider the serialized graph 0,1,2 1,2 2,2. The graph has a total of three nodes, and therefore contains three parts as separated by. First node is labeled as 0. Connect node 0 to both nodes 1 and 2. Second node is labeled as 1. Connect node 1 to node 2. Third node is labeled as 2. Connect node 2 to node 2 (itself), thus forming a self-cycle. Visually, the graph looks like the following: BFS+Hash : 时间复杂度 O(E), 空间复杂度 O(V) 采用 BFS 一个个的搜集图的顶点, 然后新建顶点和原有顶点通过 hash 建立一一映射关系方便后来构造 neighbors 1 UndirectedGraphNode * clonegraph( UndirectedGraphNode * node) { 2 if(!node) return NULL; 3 unordered_map < UndirectedGraphNode*, UndirectedGraphNode* > table; 4 unordered_set < UndirectedGraphNode* > visited; 5 queue < UndirectedGraphNode* > q; 6 q.push(node); 7 UndirectedGraphNode * new_pos, * pos; 8 while(!q.empty()){ 9 pos = q.front(); 10 q.pop(); 11 new_pos = new UndirectedGraphNode(pos->label); 12 table[pos] = new_pos; 13 for(int i = 0; i < pos->neighbors.size(); i++){ 14 if(table.count(pos->neighbors[i]) == 0){ 15 q.push(pos->neighbors[i]); 16 } 17 }

88 6.3 相关问题 } 19 q.push(node); 20 visited.insert(node); 21 while(!q.empty()){ 22 pos = q.front(); 23 q.pop(); 24 for(int i = 0; i < pos->neighbors.size(); i++){ 25 table[pos]->neighbors.push_back(table[pos->neighbors[i]]); 26 if(visited.count(pos->neighbors[i]) == 0){ 27 q.push(pos->neighbors[i]); 28 visited.insert(pos->neighbors[i]); 29 } 30 } 31 } 32 return table[node]; 33 } Course Schedule There are a total of n courses you have to take, labeled from 0 to n - 1. Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1] Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses? (leetcode 207) 举例 : 2, [[1,0]] There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible. 2, [[1,0],[0,1]] There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible. BFS : 时间复杂度 O(E), 空间复杂度 O(V) 这是一题典型的拓扑排序问题, 可以使用 BFS/DFS 进行排序. 1 bool canfinish(int numcourses, vector<vector<int>>& prerequisites) { 2 vector<vector<int> > g(numcourses, vector<int>()); 3 vector<bool> visited(numcourses, false); 4 vector<int> pre_count(numcourses, 0); 5 for(int i = 0; i < prerequisites.size(); i++){ 6 g[prerequisites[i][1]].push_back(prerequisites[i][0]); 7 pre_count[prerequisites[i][0]]++; 8 } 9 queue<int> pre; 10 for(int i = 0; i < numcourses; i++) 11 if(!pre_count[i]){ 12 pre.push(i); 13 visited[i] = true; 14 } 15 while(!pre.empty()){

89 6.3 相关问题 int pos = pre.front(); 17 pre.pop(); 18 for(int i = 0; i < g[pos].size(); i++){ 19 pre_count[g[pos][i]]--; 20 if(pre_count[g[pos][i]] < 0) return false; 21 if(pre_count[g[pos][i]] == 0){ 22 pre.push(g[pos][i]); 23 visited[g[pos][i]] = true; 24 } 25 } 26 } 27 for(int i = 0; i < numcourses; i++) 28 if(!visited[i]) return false; 29 return true; 30 } Course Schedule II There are a total of n courses you have to take, labeled from 0 to n - 1. Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1] Given the total number of courses and a list of prerequisite pairs, return the ordering of courses you should take to finish all courses. There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array. (leetcode 210) 举例 : 2, [[1,0]] There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1] 4,[[1,0],[2,0],[3,1],[3,2]] There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3]. BFS : 时间复杂度 O(E), 空间复杂度 O(V) 这题和上题一样 1 vector<int> findorder(int numcourses, vector<pair<int, int>>& prerequisites) { 2 vector<int> result; 3 vector<int> input(numcourses, 0); 4 vector<vector<int> > graph(numcourses, vector<int>()); 5 for(auto ele : prerequisites){ 6 input[ele.first]++; 7 graph[ele.second].push_back(ele.first); 8 } 9 queue<int> q; 10 for(int i = 0; i < input.size(); i++)

90 6.3 相关问题 if(input[i] == 0) q.push(i); 12 while(!q.empty()){ 13 int cur = q.front(); 14 q.pop(); 15 result.push_back(cur); 16 for(auto ele : graph[cur]) 17 if(--input[ele] == 0) q.push(ele); 18 } 19 return result.size() == numcourses? result : vector<int>(); 20 }

91 第 7 章哈希 7.1 基本概念 TODO 82

92 7.2 Hash 表的设计与实现 Hash 表的设计与实现 TODO

93 7.3 相关问题 相关问题 Substring with Concatenation of All Words You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters. (leetcode 30) 举例 : Given: s: barfoothefoobarman words: [ foo, bar ] You should return the indices: [0, 9]. Hash : 时间复杂度 O(n), 空间复杂度 O(m) 使用 hash 表记录已经收集的词及其位置以便及时更新 1 vector<int> findsubstring(string S, vector<string> &L) { 2 int n = S.size(), m = L.size(); 3 vector<int> result; 4 if(m == 0 n == 0 n < m*l[0].size()) return result; 5 6 unordered_map<string, int> table, find; 7 for(int i = 0; i < L.size(); i++){ 8 if(table.count(l[i])) table[l[i]]++; 9 else table[l[i]] = 1; 10 } 11 int start = 0, step = L[0].size(), end = 0; while(start + m*step <= n){ 14 string cur = S.substr(end, step); 15 if(!table.count(cur) (find.count(cur) && find[cur] == table[ cur]) ){ 16 start ++; 17 end = start; 18 find.clear(); 19 }else{ 20 if(!find.count(cur)) find[cur] = 1; 21 else find[cur]++; 22 end += step; 23 } 24 if(end - start == m*step) 25 result.push_back(start); 26 } 27 return result; 28 } Valid Sudoku Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules. The Sudoku board could be partially filled, where empty cells are filled with the character

94 7.3 相关问题 85.. (leetcode 36) Note: A valid Sudoku board (partially filled) is not necessarily solvable. Only the filled cells need to be validated. Hash : 时间复杂度 O(???), 空间复杂度 O(1) 采用 hash 来简化行, 列和区是否有重复, 当然这里采用的 hash 结构是用 vector 模拟的 1 bool preprocess(vector<vector<bool> > &row, 2 vector<vector<bool> > &col, 3 vector<vector<bool> > &area, 4 vector<vector<char> > &board){ 5 for(int i = 0; i < 9; i++){ 6 for(int j = 0; j < 9; j++){ 7 if(board[i][j]!=. ){ 8 if(row[i][board[i][j] - 1 ] == false) 9 return false; 10 row[i][board[i][j] - 1 ] = false; 11 } 12 } 13 } for(int j = 0; j < 9; j++){ 16 for(int i = 0; i < 9; i++){ 17 if(board[i][j]!=. ){ 18 if(col[j][board[i][j] - 1 ] == false) 19 return false; 20 col[j][board[i][j] - 1 ] = false; 21 } 22 } 23 } for(int i = 0; i < 9; i++){ 26 for(int j = 0; j < 9; j++){

95 7.3 相关问题 if(board[i][j]!=. ){ 28 if(area[(i/3)*3 + j/3][board[i][j] - 1 ] == false) 29 return false; 30 area[(i/3)*3 + j/3][board[i][j] - 1 ] = false; 31 } 32 } 33 } return true; 36 } bool isvalidsudoku(vector<vector<char> > &board) { 39 vector<vector<bool> > row(9, vector<bool>(9, true)); 40 vector<vector<bool> > col(9, vector<bool>(9, true)); 41 vector<vector<bool> > area(9, vector<bool>(9, true)); return preprocess(row, col, area, board); 44 } Isomorphic Strings Given two strings s and t, determine if they are isomorphic. Two strings are isomorphic if the characters in s can be replaced to get t. All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself. (leetcode 205) 举例 : Given egg, add, return true. Given foo, bar, return false. Given paper, title, return true. Note: You may assume both s and t have the same length. Hash : 时间复杂度 O(n), 空间复杂度 O(1) 1 bool isisomorphic(string s, string t) { 2 vector<int> table(256, -1), used(256, false); 3 for(int i = 0; i < s.size(); i++){ 4 if(table[(unsigned int)s[i]] == -1){ 5 if(used[(unsigned int)t[i]]) return false; 6 used[(unsigned int)t[i]] = true; 7 table[(unsigned int)s[i]] = (unsigned int)t[i]; 8 } 9 if(table[(unsigned int)s[i]]!= (unsigned int)t[i]) 10 return false; 11 } 12 return true; 13 }

96 7.3 相关问题 Contains Duplicate Given an array of integers, find if the array contains any duplicates. Your function should return true if any value appears at least twice in the array, and it should return false if every element is distinct. (leetcode 217) Hash : 时间复杂度 O(n), 空间复杂度 O(n) 1 bool containsduplicate(vector<int>& nums) { 2 unordered_set<int> table; 3 for(int i = 0; i < nums.size(); i++){ 4 if(table.count(nums[i])) return true; 5 table.insert(nums[i]); 6 } 7 return false; 8 } Two Sum Given an array of integers, find two numbers such that they add up to a specific target number. The function twosum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based. You may assume that each input would have exactly one solution. (leetcode 1) 举例 : Input: numbers=2, 7, 11, 15, target=9 Output: index1=1, index2=2 哈希 : 时间复杂度 O(n), 空间复杂度 O(n) 1 vector<int> twosum(vector<int> &numbers, int target) { 2 unordered_map<int, int> table; 3 for(int i = 0; i < numbers.size(); i++) 4 table[numbers[i]] = i + 1; 5 for(int i = 0; i < numbers.size(); i++){ 6 if(table.count(target - numbers[i]) && table[target - numbers[i ]]!= i + 1) 7 return vector<int>{i+1, table[target - numbers[i]]}; 8 } 9 return vector<int>{0, 0}; 10 } 这题如果是问是否存在, 可以采用 two pointer 夹逼法, 这样时间复杂度减少到了 O(1) Sum Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero. (leetcode 15)

97 7.3 相关问题 88 举例 : Given array S = , A solution set is: (-1, 0, 1) (-1, -1, 2) Note: Elements in a triplet (a,b,c) must be in non-descending order. (ie, adbdc) The solution set must not contain duplicate triplets. Hash : 时间复杂度 O(n 2 ), 空间复杂度 O(n) 为了避免统计结果出现重复, 可以先对原数组进行预处理, 排序然后去掉重复元素, 再开始利用哈希表排查出 3 点对 1 vector<vector<int> > threesum(vector<int> &num) { 2 vector<vector<int> > result; 3 if(num.size() < 3) return result; 4 unordered_map<int, int> table; 5 sort(num.begin(), num.end()); 6 vector<pair<int, int> > shrink; 7 int last = num[0], pos = 0; 8 table[num[0]] = 1; 9 shrink.push_back(make_pair(num[0], 1)); 10 for(int i = 1; i < num.size(); i++){ 11 if(last == num[i]){ 12 shrink[pos].second++; 13 table[ last]++; 14 }else{ 15 shrink.push_back(make_pair(num[i], 1)); 16 last = num[i]; 17 table[last] = 1; 18 pos ++; 19 } 20 } 21 for(int i = 0; i < shrink.size(); i++) 22 for(int j = i; j < shrink.size(); j++){ 23 if(j == i && shrink[i].second == 1) continue; 24 int left = 0 - shrink[i].first - shrink[j].first; 25 if(table.count(left) == 0) continue; 26 if(left < shrink[j].first) break; 27 if(left == shrink[j].first && j == i 28 && shrink[j].second == 2) break; 29 if(left == shrink[j].first && shrink[j].second == 1) break; 30 result.push_back(vector<int>{shrink[i].first, shrink[j]. first, left}); 31 } 32 return result; 33 } Sum Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target. Elements in a quadruplet (a,b,c,d) must be in non-descending order. (ie, adbdcdd)

98 7.3 相关问题 89 The solution set must not contain duplicate quadruplets. (leetcode 16) Given array S = , and target = 0. A solution set is: (-1, 0, 0, 1) (-2, -1, 1, 2) (-2, 0, 0, 2) Hash : 时间复杂度 O(n 2 ), 空间复杂度 O(n 2 ) 这里同样先排序, 然后需要注意重复解 1 vector<vector<int> > foursum(vector<int> &num, int target) { 2 int n = num.size(); 3 vector<vector<int> > result; 4 unordered_map<int,vector<int> > table; 5 sort(num.begin(), num.end()); 6 for(int i = 0; i < n - 1; i++){ 7 if(num[i] == num[i+1]){ 8 while(i < n - 1 && num[i] == num[i+1]) i++; 9 i--; 10 table[num[i] + num[i+1]].push_back(i); 11 continue; 12 } 13 for(int j = i + 1; j < n; j++){ 14 if(j < n - 1 && num[j] == num[j+1]){ 15 table[num[i] + num[j]].push_back(i); 16 while(j < n - 1 && num[j] == num[j+1]) j++; 17 continue; 18 } 19 table[num[i] + num[j]].push_back(i); 20 } 21 } 22 for(int i = 0; i < n - 3; i++){ 23 if(num[i] == num[i+1]){ 24 int left = target - num[i] - num[i+1]; 25 if(table.count(left)){ 26 for(int k = 0; k < table[left].size(); k++) 27 if(table[left][k] > i+1){ 28 int third = num[table[left][k]]; 29 int fourth = left - third; 30 result.push_back(vector<int>{num[i], num[i+1], third, fourth}); 31 } 32 } 33 while(i < n - 1 && num[i] == num[i+1]) i++; 34 i--; 35 continue; 36 } 37 for(int j = i + 1; j < n; j++){ 38 if(j < n - 1 && num[j] == num[j+1]){ 39 int left = target - num[i] - num[j]; 40 if(table.count(left)){ 41 for(int k = 0; k < table[left].size(); k++) 42 if(table[left][k] > j){ 43 int third = num[table[left][k]];

99 7.3 相关问题 int fourth = left - third; 45 result.push_back(vector<int>{num[i], num[j], third, fourth}); 46 } 47 } 48 while(j < n - 1 && num[j] == num[j+1]) j++; 49 continue; 50 } 51 int left = target - num[i] - num[j]; 52 if(table.count(left)){ 53 for(int k = 0; k < table[left].size(); k++) 54 if(table[left][k] > j){ 55 int third = num[table[left][k]]; 56 int fourth = left - third; 57 result.push_back(vector<int>{num[i], num[j], third, fourth}); 58 } 59 } 60 } 61 } 62 return result; 63 } Longest Consecutive Sequence Given an unsorted array of integers, find the length of the longest consecutive elements sequence. (leetcode 128) 举例 : Given [100,4,200,1,3,2], The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4. Your algorithm should run in O(n) complexity. Hash : 时间复杂度 O(n), 空间复杂度 O(n) 先用 hash 存好所有数据, 然后连续序列就像连通分量一样会被 BFS 搜出来, 只要统计出最大那个连通分量即可 1 int longestconsecutive(vector<int> &num) { 2 unordered_set<int> table; 3 for(int i = 0; i < num.size(); i++) 4 table.insert(num[i]); 5 unordered_set<int> used; 6 int ret = 0; 7 for(int i = 0; i < num.size(); i++){ 8 if(used.count(num[i]) == 0){ 9 used.insert(num[i]); 10 int con = 1; 11 for(int j = 1; ; j++){ 12 if(table.count(num[i] + j) == 0) break; 13 con ++; 14 used.insert(num[i] + j); 15 } 16 for(int j = 1; ; j++){ 17 if(table.count(num[i] - j) == 0) break;

100 7.3 相关问题 con ++; 19 used.insert(num[i] - j); 20 } 21 if(ret < con) ret = con; 22 } 23 } 24 return ret; 25 }

101 第 8 章其他数据结构 8.1 堆 堆在 STL 里面其实是不存在的容器, 它是建立在 vector 基础之上, 配合着 make h eap,push h eapmpop h eapsort h eap 这四个泛型算法来进行操作的 Implement Heap Algorithm TODO 92

102 8.2 字典树 字典树 字典树又称单词查找树,Trie 树, 是一种树形结构, 是一种哈希树的变种字典树要求根节点不包含字符, 除根节点外每一个节点都只包含一个字符 ; 从根节点到某一节点, 路径上经过的字符连接起来, 为该节点对应的字符串 ; 每个节点的所有子节点包含的字符都不相同字典树的主要用途 : 第一 : 词频统计. 它相对于使用 hash 记数的方法更加节省空间, 因为相同前缀共用. 第二 : 前缀匹配. 只要建立好字典树, 可以查找出任意公共前缀的字符串, 更加高效 Implement Trie Implement a trie with insert, search, and startswith methods. (leetcode 208) Note: You may assume that all inputs are consist of lowercase letters a-z. Trie : 时间复杂度 O(h), 空间复杂度 O(h) 字典树最简单的实现就是看做一个多叉树. 1 class TrieNode { 2 public: 3 char val; 4 bool isnode; 5 TrieNode * sons[26]; 6 public: 7 // Initialize your data structure here. 8 TrieNode(char val = -, bool isnode = false) 9 : val(val), isnode(isnode) { 10 for(int i = 0; i < 26; i++) 11 sons[i] = NULL; 12 } 13 }; class Trie { public: Trie() { 20 root = new TrieNode(); 21 } // Inserts a word into the trie. 24 void insert(string s) { 25 TrieNode *pos = root; 26 for(int i = 0; i < s.size(); i++){ 27 int index = s[i] - a ; 28 if(pos->sons[index] == NULL) 29 pos->sons[index] = new TrieNode(s[i]); 30 pos = pos->sons[index]; 31 } 32 pos->isnode = true; 33 } // Returns if the word is in the trie. 36 bool search(string key) {

103 8.2 字典树 TrieNode *pos = root; 38 for(int i = 0; i < key.size(); i++){ 39 int index = key[i] - a ; 40 if(pos->sons[index] == NULL) 41 return false; 42 pos = pos->sons[index]; 43 } 44 return pos->isnode; 45 } // Returns if there is any word in the trie 48 // that starts with the given prefix. 49 bool startswith( string prefix) { 50 TrieNode *pos = root; 51 for(int i = 0; i < prefix.size(); i++){ 52 int index = prefix[i] - a ; 53 if(pos->sons[index] == NULL) 54 return false; 55 pos = pos->sons[index]; 56 } 57 return true; 58 } private: 61 TrieNode* root; 62 };

104 8.3 其他 其他 LRU Cache Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set. get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item. (leetcode 146) List+Hash : 时间复杂度 O(1), 空间复杂度 O(1) 这题是一题非常经典的题目, 必须非常熟悉各个数据结构的优劣点才能想到怎么去结合它们设计出 O(1) 操作的 LRU Cache. 我们知道 Hash 的好处是可以 O(1) 时间的定位和插入与删除, 坏处是数据存储位置难以猜测, 彼此独立 ; 数组的优点是 O(1) 访问, 存储连续, 但是缺点是无法 O(1) 的插入和删除 ; 链表和数组相反, 插入删除很方便, 但是访问就很慢. 回到这个问题, 我们想一下首先我们要 O(1) 时间访问元素即 get(key), 那么必须要有哈希结构 ; 然后, 我们在 insert 元素时候, 当 cache 满的时候我们需要花 O(1) 的时间直接找到哪个是最老的, 那我们自然想到了排序, 我们可以试着每次插入元素后, 都把对应插入时间插入到一个排序的顺序容器中, 由于我们还有 set 操作, 所以我们不能使用 vector 来盛放这个排序序列, 因为中间删除代价非常大, 只能使用 list, 当然我们需要在 hash 表上记录该元素所在 list 的 ListNode 的指针方便一次定位. 1 class LRUCache { 2 private: 3 struct ListNode{ 4 int key; 5 int value; 6 ListNode *pre, *next; 7 ListNode(int key = -1, int value = -1) : key(key), value(value), pre(null), next(null) 8 {} 9 }; unordered_map<int, ListNode*> table; 12 ListNode seq_ head; 13 ListNode seq_end; 14 int capacity; 15 int size; private: 18 void seq_push_back(listnode *pos){ 19 seq_end.pre->next = pos; 20 pos->pre = seq_end.pre; 21 seq_end.pre = pos; 22 pos->next = &seq_end; 23 } void seq_erase(listnode *pos){ 26 pos->pre->next = pos->next; 27 pos->next->pre = pos->pre; 28 } 29

105 8.3 其他 public: LRUCache(int capacity) : capacity(capacity){ 33 size = 0; 34 seq_head.next = &seq_end; 35 seq_end.pre = &seq_head; 36 } int get(int key){ 39 if(table.count(key) == 0) return -1; 40 seq_erase(table[key]); 41 seq_push_back(table[key]); 42 return table[key]->value; 43 } void set(int key, int value){ 47 if(table.count(key)){ 48 table[key]->value = value; 49 seq_erase(table[key]); 50 seq_push_back(table[key]); 51 }else{ 52 if(size >= capacity){ 53 ListNode *old = seq_head.next; 54 table.erase(old->key); 55 seq_erase(old); 56 delete old; 57 size--; 58 } 59 size ++; 60 table[key] = new ListNode(key, value); 61 seq_push_back(table[key]); 62 } 63 } };

106 第 9 章排序 9.1 基本概念 排序是最基本的算法, 将一个集合里面的元素按照一定顺序排列. 排序算法又分为稳定和非稳定的排序算法, 不同的排序算法对时间和空间的考虑都不同. 97

107 9.2 经典排序算法 经典排序算法 经典的排序算法及其各种属性, 这里先总结一下 : 排序算法时间复杂度空间复杂度稳定性 备注 插入排序 O(n 2 ) O(1) 稳定 冒泡排序 O(n 2 ) O(1) 稳定 选择排序 O(n 2 ) O(1) 不稳定 归并排序 O(nlgn) O(n) 稳定 堆排序 O(nlgn) O(1) 不稳定 快速排序 O(nlgn) O(1) 不稳定 最差时间复杂度为 O(n 2 ) 计数排序 O(n) O(m) 只局限于处于 [0,m) 的整数排序 桶排序 O(n) O(n) 只局限于能通过类似位分割的排序对象 表 9.1: 排序算法比较 Insert Sort 1 void insert_sort(int A[], int n){ 2 if(n <= 1) return; 3 int cur; 4 for(int i = n - 2; i >= 0; i--){ 5 cur = A[i]; 6 int j = i + 1; 7 for(; j < n && cur > A[j]; j++) 8 A[j-1] = A[j]; 9 A[j-1] = cur; 10 } 11 } Bubble Sort 1 void swap(int& a, int& b){ 2 //avoid a, b is the same one 3 if(a == b) return; 4 a = a^b; 5 b = a^b; 6 a = a^b; 7 } 8 9 void bubble_sort(int A[], int n){ 10 if(n <= 1) return; 11 for(int i = n - 1; i > 0; i--){ 12 for(int j = 0; j < i; j++){ 13 if(a[j] > A[j+1]) 14 swap(a[j], A[j+1]); 15 } 16 } 17 } Selection Sort

108 9.2 经典排序算法 99 1 void select_sort(int A[], int n){ 2 if(n <= 1) return; 3 for(int i = 0; i < n; i++){ 4 int minpos = i; 5 for(int j = i + 1; j < n; j++){ 6 if(a[minpos] > A[j]){ 7 minpos = j; 8 } 9 } 10 swap(a[i], A[minPos]); 11 } 12 } Merge Sort Merge Sort 这里分为递归和迭代两种实现, 关于迭代的实现可以参考博客归并排序的迭代写法 1 // 递归版 merge sort int min(int a, int b){ 3 return a < b? a : b; 4 } 5 6 void merge_aux(int A[], int begin, int end){ 7 if(begin >= end) return; 8 int mid = (begin + end)/2; 9 merge_aux(a, begin, mid); 10 merge_aux(a, mid+1, end); 11 int result[end - begin + 1]; 12 int i = begin, j = mid+1, k = 0; 13 while(i <= mid j <= end){ 14 int first = i > mid? INT_MAX : A[i]; 15 int second = j > end? INT_MAX : A[j]; 16 if(first < second) i++; 17 else j++; 18 result[k++] = min(first, second); 19 } 20 for(int i = 0; i < k; i++) 21 A[begin + i] = result[i]; 22 } void merge_sort(int A[], int n){ 25 merge_aux(a, 0, n-1); 26 } 27 // // 迭代版 merge sort void merge_iteration(vector<int> &mix, vector<int> &cur){ 30 vector<int> sum; 31 int i = 0, j = 0; 32 while(i < mix.size() j < cur.size()){ 33 int left = i == mix.size()? INT_MAX : mix[i]; 34 int right = j == cur.size()? INT_MAX : cur[j]; 35 if(left < right) i++; 36 else j++; 37 sum.push_back(min(left, right)); 38 } 39 cur.clear(); 40 mix = sum;

109 9.2 经典排序算法 } void merge_sort(int A[], int n){ 44 if(n <= 1) return; 45 vector<vector<int> > bucket(64, vector<int>()); 46 for(int i = 0; i < n; i++){ 47 int index = 0; 48 vector<int> mix(1, A[i]); 49 while(!bucket[index].empty()){ 50 merge_iteration(mix, bucket[index]); 51 index ++; 52 } 53 bucket[index] = mix; 54 } 55 vector<int> mix; 56 for(int i = 0; i < 64; i++){ 57 if(!bucket[i].empty()){ 58 merge_iteration(mix, bucket[i]); 59 } 60 } 61 for(int i = 0; i < mix.size(); i++) 62 A[i] = mix[i]; 63 } Heap Sort 1 void adjust_heap(int A[], int n, int root){ 2 while(1){ 3 int left = root*2 + 1 > n - 1? INT_MIN : A[root*2 + 1]; 4 int right = root*2 + 2 > n - 1? INT_MIN : A[root*2 + 2]; 5 if(a[root] < max(left, right)){ 6 if(left < right){ 7 swap(a[root], A[root*2+2]); 8 root = root*2 + 2; 9 }else{ 10 swap(a[root], A[root*2+1]); 11 root = root*2 + 1; 12 } 13 }else 14 break; 15 } 16 } // build max heap 19 void make_heap(int A[], int n){ 20 for(int i = n/2-1; i >= 0; i--){ 21 adjust_heap(a, n, i); 22 } 23 } void pop_heap(int A[], int n){ 26 if( n <= 1) return; 27 swap(a[0], A[n-1]); 28 adjust_heap(a, n-1, 0); 29 } void heap_sort(int A[], int n){

110 9.2 经典排序算法 make_heap(a, n); 33 for(int i = n; i > 0; i--) 34 pop_heap(a, i); 35 } Quick Sort 1 int partition(int A[], int begin, int end){ 2 int index = rand()%(end - begin + 1) + begin; 3 int last = begin; 4 swap(a[index], A[end]); 5 for(int i = begin; i < end; i++){ 6 if(a[i] < A[end]){ 7 swap(a[last], A[i]); 8 last ++; 9 } 10 } 11 swap(a[end], A[last]); 12 return last; 13 } void quick_aux(int A[], int begin, int end){ 16 if(begin < end){ 17 int part = partition(a, begin, end); 18 quick_aux(a, begin, part - 1); 19 quick_aux(a, part + 1, end); 20 } 21 } void quick_sort(int A[], int n){ 24 quick_aux(a, 0, n-1); 25 } Count Sort 1 //assume ele in A is [0, up) 2 //up is not a very large num 3 void count_sort(int A[], int n, int up){ 4 int count[up]; 5 memset(count, 0, sizeof(count)); 6 for(int i = 0; i < n; i++) 7 count[a[i]]++; 8 int pos = 0; 9 for(int i = 0; i < up; i++){ 10 for(int j = 0; j < count[i]; j++){ 11 A[pos++] = i; 12 } 13 } 14 } Bucket Sort 桶排序实现起来比较复杂, 这里思路是建立 10 个头链表和尾链表指针, 分别维护 0,1,...,9 这个 10 个桶, 从低位开始逐渐调整排序, 把相应数据放入到相应桶中.

111 9.2 经典排序算法 struct bucket_ node{ 2 int val; 3 bucket_node* next; 4 }; 5 6 int march_bucket(int val, int pos){ 7 return (val / ((int)pow(10,pos))) % 10; 8 } 9 10 void bucket_aux(bucket_node start[], bucket_node end[], int n, int pos){ 11 bucket_ node new_ start[10], new_end[10]; 12 for(int i = 0; i < 10; i++){ 13 new_start[i].next = 0; 14 new_end[i].next = &new_start[i]; 15 } 16 for(int i = 0; i < n; i++){ 17 bucket_node *head = &start[i], *cur; 18 while(head->next){ 19 cur = head->next; 20 head->next = cur->next; 21 int id = march_bucket(cur->val, pos); 22 (new_end[id].next)->next = cur; 23 cur->next = 0; 24 new_end[id].next = cur; 25 } 26 } 27 for(int i = 0; i < 10; i++){ 28 start[i] = new_start[i]; 29 end[i] = new_end[i]; 30 } 31 } void bucket_pre(int A[], bucket_node node[], int n, int w){ 34 bucket_ node start[10], end[10]; 35 start[0].next = &node[0]; 36 end[0].next = &node[n-1]; 37 for(int i = 1; i < 10; i++){ 38 start[i].next = 0; 39 end[i].next = &start[i]; 40 } 41 for(int i = 0; i < w; i++) 42 bucket_aux(start, end, 10, i); 43 int pos = 0; 44 for(int i = 0; i < 10; i++){ 45 bucket_node* cur = &start[i]; 46 while(cur->next){ 47 cur = cur->next; 48 A[pos++] = cur->val; 49 } 50 } 51 } void bucket_sort(int A[], int n, int w){ 54 if(n <= 1) return; 55 bucket_node node[n]; 56 bucket_node* pre = &node[0]; ; 57 pre->val = A[0]; 58 for(int i = 1; i < n; i++){

112 9.2 经典排序算法 node[i].val = A[i]; 60 pre->next = &node[i]; 61 pre= pre->next; 62 } 63 pre->next = 0; 64 bucket_pre(a, node, n, w); 65 }

113 9.3 基于排序的问题 基于排序的问题 Merge Intervals Given a collection of intervals, merge all overlapping intervals.(leetcode 56) 举例 : Given [1,3],[2,6],[8,10],[15,18], return [1,6],[8,10],[15,18]. 排序 : 时间复杂度 O(nlgn), 空间复杂度 O(1) 这里先排序, 然后逐个的进行合并 1 class mysort{ 2 public: 3 bool operator()(interval a, Interval b){ 4 return a.start < b.start; 5 } 6 }; 7 8 vector<interval> merge(vector<interval> &intervals) { 9 vector<interval> result; 10 sort(intervals.begin(), intervals.end(), mysort()); 11 Interval cur; 12 int pos = 0; 13 while(pos < intervals.size()){ 14 cur = intervals[pos]; 15 pos ++; 16 while(pos < intervals.size() && cur.end >= intervals[pos].start) { 17 cur.end = max(cur.end, intervals[pos].end); 18 pos ++; 19 } 20 result.push_back(cur); 21 } 22 return result; 23 } Insert Interval Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary). You may assume that the intervals were initially sorted according to their start times. (leetcode 57) 举例 : Given intervals [1,3],[6,9], insert and merge [2,5] in as [1,5],[6,9]. Given [1,2],[3,5],[6,7],[8,10],[12,16], insert and merge [4,9] in as [1,2],[3,10],[12,16]. This is because the new interval [4,9] overlaps with [3,5],[6,7],[8,10]. 排序 : 时间复杂度 O(nlgn), 空间复杂度 O(1) 先排序, 然后找到插入点, 插入进去. 1 vector<interval> insert(vector<interval> &intervals, Interval newinterval) {

114 9.3 基于排序的问题 vector<interval> result; 3 Interval cur = newinterval; 4 int pos = 0; 5 bool ismerge = false; 6 while(pos < intervals.size()){ 7 if(ismerge cur.start > intervals[pos].end){ 8 result.push_back(intervals[pos]); 9 pos ++; 10 }else{ 11 if(cur.end >= intervals[pos].start){ 12 cur.start = min(cur.start, intervals[pos].start); 13 while(pos < intervals.size() && cur.end >= intervals[pos ].start){ 14 cur.end = max(intervals[pos].end, cur.end); 15 pos ++; 16 } 17 } 18 result.push_back(cur); 19 ismerge = true; 20 } 21 } 22 if(!ismerge) result.push_back(cur); 23 return result; 24 } Sort Colors Given an array with n objects colored red, white or blue, sort them so that objects of the same color are adjacent, with the colors in the order red, white and blue. Here, we will usetheintegers 0, 1, and2torepresentthecolor red, white, andblue respectively. (leetcode 75) Note: You are not suppose to use the library s sort function for this problem. Follow up: A rather straight forward solution is a two-pass algorithm using counting sort. First, iterate the array counting number of 0 s, 1 s, and 2 s, then overwrite array with total number of 0 s, then 1 s and followed by 2 s. Could you come up with an one-pass algorithm using only constant space? 排序 : 时间复杂度 O(n), 空间复杂度 O(m) 这题第一反应就是使用计数排序, 但是 Follow up 说只能 pass 一次元素, 所以只能采用一点小技巧了 : 把 0 一直往前放, 把 2 一直往后放, 这样剩余的 1 就在中间了. 1 void sortcolors(int A[], int n) { 2 if(n <= 1) return; 3 int zero_pos = 0, two_pos = n - 1; 4 for(int i = 0; i <= two_pos;){ 5 if(a[i] == 0){ 6 if(zero_pos!= i) swap(a[zero_pos], A[i]); 7 else i++; 8 zero_ pos ++; 9 }else if(a[i] == 2){ 10 if(two_pos!= i) swap(a[two_pos], A[i]);

115 9.3 基于排序的问题 else i++; 12 two_pos--; 13 }else{ 14 i++; 15 } 16 } 17 } Maximum Gap Given an unsorted array, find the maximum difference between the successive elements in its sorted form. Try to solve it in linear time/space. Return 0 if the array contains less than 2 elements. You may assume all elements in the array are non-negative integers and fit in the 32-bit signed integer range.(leetcode 164) 排序 : 时间复杂度 O(n), 空间复杂度 O(n) 这题要求 O(n) 的时间和空间复杂度, 而且可以看到元素都是 32-bit 有符号正数, 所以想到使用桶排序. 1 struct bucket_ node{ 2 int val; 3 bucket_node* next; 4 }; 5 6 int march_bucket(int val, int pos){ 7 return (val / ((int)pow(10,pos))) % 10; 8 } 9 10 void bucket_aux(bucket_node start[], bucket_node end[], int n, int pos){ 11 bucket_ node new_ start[10], new_end[10]; 12 for(int i = 0; i < 10; i++){ 13 new_start[i].next = 0; 14 new_end[i].next = &new_start[i]; 15 } 16 for(int i = 0; i < n; i++){ 17 bucket_node *head = &start[i], *cur; 18 while(head->next){ 19 cur = head->next; 20 head->next = cur->next; 21 int id = march_bucket(cur->val, pos); 22 (new_end[id].next)->next = cur; 23 cur->next = 0; 24 new_end[id].next = cur; 25 } 26 } 27 for(int i = 0; i < 10; i++){ 28 start[i] = new_start[i]; 29 end[i] = new_end[i]; 30 } 31 } void bucket_pre(vector<int> &A, bucket_node node[], int n, int w){ 34 bucket_ node start[10], end[10]; 35 start[0].next = &node[0];

116 9.3 基于排序的问题 end[0].next = &node[n-1]; 37 for(int i = 1; i < 10; i++){ 38 start[i].next = 0; 39 end[i].next = &start[i]; 40 } 41 for(int i = 0; i < w; i++) 42 bucket_aux(start, end, 10, i); 43 int pos = 0; 44 for(int i = 0; i < 10; i++){ 45 bucket_node* cur = &start[i]; 46 while(cur->next){ 47 cur = cur->next; 48 A[pos++] = cur->val; 49 } 50 } 51 } int maximumgap(vector<int> &num) { 54 int n = num.size(); 55 if(n <= 1) return 0; 56 bucket_node node[n]; 57 bucket_node* pre = &node[0]; ; 58 pre->val = num[0]; 59 for(int i = 1; i < n; i++){ 60 node[i].val = num[i]; 61 pre->next = &node[i]; 62 pre= pre->next; 63 } 64 pre->next = 0; 65 bucket_pre(num, node, n, 10); 66 int gap = INT_MIN; 67 for(int i = 1; i < n; i++) 68 if(gap < num[i] - num[i-1]) 69 gap = num[i] - num[i-1]; 70 return gap; 71 } Largest Number Given a list of non negative integers, arrange them such that they form the largest number. (leetcode 179) 举例 : given [3, 30, 34, 5, 9], the largest formed number is Note: The result may be very large, so you need to return a string instead of an integer. 排序 : 时间复杂度 O(nlgn), 空间复杂度 O(n*m) 这题最主要是对元素进行排序, 排序的关键是如何判断两个元素 谁大谁小, 判断的标准是 ab 与 ba 大小对比. 另外需要注意的是大数据下该用 vector/string 这样来操作. 1 static int march_bucket(int val, int pos){ 2 return (val / ((int)pow(10,pos))) % 10; 3 } 4 5 class mysort{ 6 public: 7 bool cmp(vector<int> &a, vector<int> &b){

117 9.3 基于排序的问题 int i = a.size() - 1; 9 for(; i >= 0 && a[i] == b[i]; i--) 10 ; 11 return i < 0? true : a[i] > b[i]; 12 } // return true if x > y 15 bool operator()(int x, int y){ 16 if(x == 0 y == 0) return x > y; 17 int i = 9, j = 9; 18 vector<int> a(10,0), b(10,0); 19 while(i >= 0){ 20 a[i] = Solution::march_bucket(x, i); 21 b[i] = Solution::march_bucket(y, i); 22 i--; 23 } 24 i = 9; 25 while(i >= 0 && a[i] == 0) 26 i--; 27 while(j >= 0 && b[j] == 0) 28 j--; 29 vector<int> ab, ba; 30 ab.insert(ab.end(), b.begin(), b.begin() + j +1); 31 ab.insert(ab.end(), a.begin(), a.begin() + i + 1); 32 ba.insert(ba.end(), a.begin(), a.begin() + i + 1); 33 ba.insert(ba.end(), b.begin(), b.begin() + j +1); 34 return cmp(ab, ba); 35 } 36 }; string genstring(int ele){ 39 string ret; 40 int i = 9; 41 bool start = false; 42 while(i >= 0){ 43 int cur = march_bucket(ele, i); 44 if(cur!= 0 &&!start) start = true; 45 if(start) ret.push_back(cur + 0 ); 46 i--; 47 } 48 if(!start) return "0"; 49 return ret; 50 } string largestnumber(vector<int> &num) { 53 sort(num.begin(), num.end(), mysort()); 54 string result; 55 for(auto ele : num){ 56 if(ele == 0 && result.empty()) 57 return "0"; 58 else 59 result = result + genstring(ele); 60 } 61 return result; 62 }

118 第 10 章二分查找 10.1 基本概念 二分查找, 是一种非常经典的快速查找算法. 抽象的来说 : 当我们在求满足某个条件 C(x) 的最小的 x 时, 对于任意满足 C(x) 的 x, 如果所有的 x 1 x 也满足 的话就可以使用二分查找来求得最小的 x. C(x 1 ) 109

119 10.2 经典问题 经典问题 二分查找有很多经典实例, 比如 STL 库中常用的 lower bound 等等

120 10.3 相关问题 相关问题 贪心算法还有很多应用场景, 下面介绍几种常见的题目 Median of Two Sorted Arrays There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)). (leetcode 4) 二分查找 : 时间复杂度 O(lgn), 空间复杂度 O(1) 这题涉及两个有序数组的查找, 主要思想都一样, 都是折半查找, 去除一部分不合法的候选, 然后不断的缩小范围查找. 1 double getmid(int A[], int start, int end){ 2 if(start > end) return 0; 3 int len = end - start + 1; 4 return len%2 == 0? (A[start + len/2-1] + A[start + len/2])/2.0 : A[ start+len/2]; 5 } 6 7 double getele(int A[], int enda, int B[], int endb, int pos){ 8 int mida = enda / 2; 9 int midb = endb / 2; if(a[mida] < B[midB]){ 12 if(mida + midb + 2 > pos + 1){ 13 if(midb - 1 < 0) return A[pos]; 14 return getele(a, enda, B, midb-1, pos); 15 } 16 else{ 17 if(mida + 1 > enda) return B[pos - mida - 1]; 18 return getele(&a[mida+1], enda - mida - 1, B, endb, pos - mida - 1); 19 } 20 }else{ 21 if(mida + midb + 2 > pos + 1){ 22 if(mida - 1 < 0) return B[pos]; 23 return getele(a, mida-1, B, endb, pos); 24 } 25 else{ 26 if(midb+1 > endb) return A[pos - midb - 1]; 27 return getele(a, enda, &B[midB+1], endb - midb - 1, pos - midb - 1); 28 } 29 } 30 } double findmediansortedarrays(int A[], int m, int B[], int n) { 33 if(m < 1 && n < 1) return 0; 34 if(m < 1 n < 1) return getmid(a, 0, m-1) + getmid(b, 0, n-1); 35 if((n+m)%2 == 0){ 36 int first = getele(a, m-1, B, n-1, (n+m)/2-1); 37 int second = getele(a, m-1, B, n-1, (n+m)/2); 38 return (first + second) / 2.0; 39 } 40 return getele(a, m-1, B, n-1, (n+m)/2);

121 10.3 相关问题 } Sum Closest Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution. (leetcode 16) 举例 : Given array S = 121 4, and target = 1. The sum that is closest to the target is 2. ( = 2). 二分查找 : 时间复杂度 O(n 2 lgn), 空间复杂度 O(1) 这题是找到最近的值, 所以 hash 就力不从心的, 我们就可以采用二分搜索的方法 1 void binarysearch(vector<int> &num, int begin, int end, 2 int target, int sum, int &result){ 3 if(begin > end) return; 4 int mid = (begin + end) / 2; 5 if(sum + num[mid] == target){ 6 result = target; 7 return; 8 } 9 if(result == INT_MAX abs(target - result) > abs(target - sum - num[mid])) 10 result = sum + num[mid]; 11 if(sum + num[mid] > target) 12 binarysearch(num, begin, mid-1, target, sum, result); 13 else 14 binarysearch(num, mid+1, end, target, sum, result); 15 } int threesumclosest(vector<int> &num, int target) { 18 sort(num.begin(), num.end()); 19 int result = INT_MAX; 20 for(int i = 0; i < num.size() - 2; i++) 21 for(int j = i + 1; j < num.size() - 1; j++){ 22 int sum = num[i] + num[j]; 23 binarysearch(num, j + 1, num.size() - 1, target, sum, result ); 24 } 25 return result; 26 } Search in Rotated Sorted Array Suppose a sorted array is rotated at some pivot unknown to you beforehand.(leetcode 33) 举例 : (i.e., might become ).

122 10.3 相关问题 113 Note: You are given a target value to search. If found in the array return its index, otherwise return -1. You may assume no duplicate exists in the array. 二分查找 : 时间复杂度 O(lgn), 空间复杂度 O(1) 同样采用二分查找, 需要注意的就是如果查找范围已经是排序好的, 需要改变查找策略. 1 int binay_search(int A[], int begin, int end, int target){ 2 if(begin > end) return -1; 3 int mid = (begin + end) / 2; 4 if(a[mid] == target) return mid; 5 if(a[mid] < target) return binay_search(a, mid+1, end, target); 6 else return binay_search(a, begin, mid-1, target); 7 } 8 9 int search_aux(int A[], int begin, int end, int target){ 10 if(begin > end) return -1; 11 if(begin == end) return A[begin] == target? begin : -1; 12 if(a[end] > A[begin]) return binay_search(a, begin, end, target); 13 int mid = (begin + end) / 2; 14 if(target == A[mid]) return mid; 15 if(a[mid] > A[begin]){ 16 if(target < A[mid] && target >= A[begin]) 17 return binay_search(a, begin, mid-1, target); 18 return search_aux(a, mid+1, end, target); 19 }else if(a[mid] == A[begin]){ 20 return A[end] == target? end : -1; 21 }else{ 22 if(target > A[mid] && target <= A[end]) 23 return binay_search(a, mid+1, end, target); 24 return search_aux(a, begin, mid-1, target); 25 } 26 } int search(int A[], int n, int target) { 29 return search_aux(a, 0, n-1, target); 30 } Search in Rotated Sorted Array II Follow up for Search in Rotated Sorted Array : What if duplicates are allowed? Would this affect the run-time complexity? How and why? Write a function to determine if a given target is in the array. (leetcode 81) 二分查找 : 时间复杂度 O(n), 空间复杂度 O(1) 这次旋转数组里面有重复数据, 那么切分时候就需要前后判断一下, 所以可能在最坏情况下需要 O(n) 时间复杂度. 1 bool binary_sarch(int A[], int n, int target){ 2 if(n < 1) return false; 3 int mid = n/2; 4 if(a[mid] == target) return true;

123 10.3 相关问题 if(a[mid] < target) return binary_sarch(&a[mid+1], n - mid -1, target); 6 else binary_sarch(a, mid, target); 7 } 8 9 bool search(int A[], int n, int target) { 10 if(n < 1) return false; 11 if(n == 1) return A[0] == target? true : false; 12 int mid = n/2; 13 if(a[mid] == target) return true; 14 if(a[mid] > A[0]){ 15 if(a[mid] > target && A[0] <= target) 16 return binary_sarch(a, mid, target); 17 return search(&a[mid+1], n - mid - 1, target); 18 }else if(a[mid] < A[0]){ 19 if(a[mid] < target && A[n-1] >= target) 20 return binary_sarch(&a[mid+1], n - mid - 1, target); 21 return search(a, mid, target); 22 }else{ 23 int pos = 0; 24 while(pos < mid && A[pos] == A[mid]) pos++; 25 if(pos == mid) return search(&a[mid+1], n - mid - 1, target ); 26 else return search(&a[pos], mid - pos, target); 27 } 28 } Search for a Range Given a sorted array of integers, find the starting and ending position of a given target value.(leetcode 34) 举例 : Given [5, 7, 7, 8, 8, 10] and target value 8, return [3, 4]. Note: Your algorithm s runtime complexity must be in the order of O(log n). If the target is not found in the array, return [-1, -1]. 二分查找 : 时间复杂度 O(lgn), 空间复杂度 O(1) 我这里的方法是先通过二分查找随便找到一个位置, 然后再找其上下界. 其实可以直接编写类似 STL 库中的 lower bound 和 upper bound 就可以了 int binary_sarch(int A[], int begin, int end, int target){ 2 if(begin > end) return -1; 3 int mid = (begin + end) / 2; 4 if(a[mid] == target) return mid; 5 if(a[mid] > target) return binary_sarch(a, begin, mid-1, target); 6 return binary_sarch(a, mid+1, end, target); 7 } 8 9 int binary_sarchno(int A[], int begin, int end, int target, bool left){ 10 if(begin > end) return left? begin : end; 11 int mid = (begin + end) / 2; 12 int new_begin, new_end;

124 10.3 相关问题 if(a[mid] == target){ 14 new_begin = left? begin : mid + 1; 15 new_end = left? mid - 1 : end; 16 }else if(a[mid] > target){ 17 return binary_sarchno(a, begin, mid - 1, target, left); 18 }else{ 19 return binary_sarchno(a, mid+1, end, target, left); 20 } 21 return binary_sarchno(a, new_begin, new_end, target, left); 22 } vector<int> searchrange(int A[], int n, int target) { 25 vector<int> result{-1, -1}; 26 int pos = binary_sarch(a, 0, n-1, target); 27 if(pos!= -1){ 28 result[0] = pos == 0? 0 : binary_sarchno(a, 0, pos, target, true ); 29 result[1] = pos == n-1? n-1 : binary_sarchno(a, pos, n-1, target, false); 30 } 31 return result; 32 } Search Insert Position Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.you may assume no duplicates in the array.(leetcode 35) 举例 : (1,3,5,6), 5 2 (1,3,5,6), 2 1 (1,3,5,6), 7 4 (1,3,5,6), 0 0 二分查找 : 时间复杂度 O(lgn), 空间复杂度 O(1) 其实就是 lower bound 函数 1 int binary_search(int A[], int begin, int end, int target){ 2 if(begin > end){ 3 return begin; 4 } 5 int mid = (begin + end) / 2; 6 if(a[mid] == target) return mid; 7 if(a[mid] > target) return binary_search(a, begin, mid-1, target); 8 return binary_search(a, mid+1, end, target); 9 } int searchinsert(int A[], int n, int target) { 12 return binary_search(a, 0, n-1, target); 13 } Divide Two Integers Divide two integers without using multiplication, division and mod operator. If it is overflow, return MAX INT. (leetcode 29)

125 10.3 相关问题 116 : 时间复杂度 O(lgn), 空间复杂度 O(1) 这题主要思路就是, 不断的通过二分查找算出一个个 dividend = x divisor = 2 i divisor, 另外需要注意的就是注意溢出问题, 这里用的技巧就是全部换算成负数. 1 int div2n(int &dividend, int divisor){ 2 if(dividend > divisor){ 3 dividend = 0; 4 return 0; 5 } 6 int i = 1; 7 while(divisor > dividend - divisor ){ 8 divisor += divisor; 9 i = i + i; 10 } 11 dividend -= divisor; 12 return i; 13 } int divide(int dividend, int divisor) { 16 bool isminus = (dividend < 0 && divisor > 0) (dividend > 0 && divisor < 0); 17 dividend = dividend < 0? dividend : -dividend; 18 divisor = divisor < 0? divisor : -divisor; 19 int result = 0; 20 while(dividend){ 21 result -= div2n(dividend, divisor); 22 } 23 return isminus? result : (result == INT_MIN? INT_MAX : -result); 24 } 2 i Pow(x, n) Implement pow(x, n). (leetcode 50) 二分查找 : 时间复杂度 O(lgn), 空间复杂度 O(1) 和除法几乎如出一辙, 都是切分成很多个, 再用二分法 2 i 1 // return x^(count) 2 double pow2n(double x, int& n){ 3 int count = 1; 4 while(count < n>>1){ 5 x = x * x; 6 count = count<<1; 7 } 8 n = n - count; 9 return x; 10 } double pow(double x, int n) { 13 if(n == 0) return 1;

126 10.3 相关问题 if(n < 0) return n == INT_MIN? 1.0/(x * pow(x, INT_MAX)) : 1.0/pow (x, -n); 15 double result = 1.0; 16 while(n) result = result * pow2n(x, n); 17 return result; 18 } Sqrt(x) Implement int sqrt(int x). (leetcode 69) 二分查找 : 时间复杂度 O(lgn), 空间复杂度 O(1) 二分搜索, 夹逼方法. 这题其实也可以使用牛顿迭代法做. 1 int sqrt(int x) { 2 if(x < 2) return x; 3 int low = 1, high = x/2; 4 while(low <= high){ 5 int mid = (low + high) / 2; 6 if(mid == x/mid) return mid; 7 if(mid > x/mid) high = mid - 1; 8 else low = mid + 1; 9 } 10 return high; 11 } 牛顿迭代法 : 时间复杂度 O(???), 空间复杂度 O(1) 1 int sqrt(int x) { 2 if(x < 2) return x; 3 int pos = x/2; 4 while(!(pos <= x/pos && (pos+1) >= x/(pos+1))){ 5 pos = pos/2 + x/(2*pos); 6 } 7 if((pos+1) == x/(pos+1)) return pos+1; 8 return pos; 9 } Search a 2D Matrix Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: Integers in each row are sorted from left to right. The first integer of each row is greater than the last integer of the previous row. (leetcode 74) 举例 : Consider the following matrix: ( (1, 3, 5, 7), (10, 11, 16, 20), (23, 30, 34, 50) ( Given target = 3, return true.

127 10.3 相关问题 118 二分查找 : 时间复杂度 O(lgn + lgm), 空间复杂度 O(1) 先查找行, 再查找列 1 bool searchmatrix(vector<vector<int> > &matrix, int target) { 2 int n = matrix.size(); 3 if(n == 0) return false; 4 int low = 0, high = n - 1; 5 int m = matrix[0].size(); 6 while(low < high){ 7 int mid = (low + high) / 2; 8 if(matrix[mid][m-1] == target) return true; 9 if(matrix[mid][m-1] > target) high = mid; 10 else low = mid + 1; 11 } 12 int cur = low; 13 low = 0, high = m - 1; 14 while(low <= high){ 15 int mid = (low + high) / 2; 16 if(matrix[cur][mid] == target) return true; 17 if(matrix[cur][mid] > target) high = mid - 1; 18 else low = mid + 1; 19 } 20 return false; 21 } Find Minimum in Rotated Sorted Array Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., might become ). Find the minimum element. You may assume no duplicate exists in the array. (leetcode 153) 二分搜索 : 时间复杂度 O(lgn), 空间复杂度 O(1) 这题和 search in a rotated array 其实解法一样 1 int binary_search(vector<int> &num, int begin, int end){ 2 if(begin >= end) return num[begin]; 3 if(num[begin] < num[end]) return num[begin]; 4 int mid = (begin + end) / 2; 5 if(num[mid] > num[begin]){ 6 return binary_search(num, mid, end); 7 }else if(num[mid] == num[begin]){ 8 return num[mid+1]; 9 }else{ 10 return binary_search(num, begin, mid); 11 } 12 } int findmin(vector<int> &num) { 15 int n = num.size(); 16 return binary_search(num, 0, n-1); 17 }

128 10.3 相关问题 Find Minimum in Rotated Sorted Array II Follow up for Find Minimum in Rotated Sorted Array : What if duplicates are allowed? Would this affect the run-time complexity? How and why? (leetcode 154) 二分搜索 : 时间复杂度 O(n), 空间复杂度 O(1) 这题和 search in a rotated array II 其实解法一样 1 int binary_search(vector<int> &num, int begin, int end){ 2 if(begin >= end) return num[end]; 3 if(num[begin] < num[end]) return num[begin]; 4 int mid = (begin + end) / 2; 5 if(num[mid] > num[begin]){ 6 return binary_search(num, mid+1, end); 7 }else if(num[mid] < num[begin]){ 8 return binary_search(num, begin, mid); 9 }else{ 10 int pos = begin; 11 while(pos < mid && num[pos] == num[mid]) pos++; 12 if(pos == mid) return binary_search(num, mid+1, end); 13 else return binary_search(num, begin + pos, mid); 14 } 15 } int findmin(vector<int> &num) { 18 int n = num.size(); 19 return binary_search(num, 0, n-1); 20 } Find Peak Element A peak element is an element that is greater than its neighbors. Given an input array where num[i] num[i+1], find a peak element and return its index. The array may contain multiple peaks, in that case return the index to any one of the peaks is fine. You may imagine that num[-1] = num[n] = -. (leetcode 162) 举例 : For example, in array [1, 2, 3, 1], 3 is a peak element and your function should return the index number 2. 二分搜索 : 时间复杂度 O(lgn), 空间复杂度 O(1) 这题同样采用切分, 然后根据丢掉一部分选择范围, 我们对于选择范围 [s, e], 取中间点 m = (s + e)/2, 判断 A[m-1], A[m] 和 A[m+1] 的关系然后选择下一次迭代范围为 [s,m-1] 还是 [m+1,e], 然后不断的缩小范围. 1 bool ispeak(const vector<int> &num, int pos){ 2 return pos == 0? num[pos] > num[pos+1] : num[pos] > num[pos-1]; 3 } 4 5 int binary_sarch(const vector<int> &num, int begin, int end){ 6 if(begin >= end) return end;

129 10.3 相关问题 int mid = (begin + end) / 2; 8 if(num[mid] > num[mid-1] && num[mid] > num[mid+1]) 9 return mid; 10 if(num[mid] >= num[begin] && num[mid] >= num[end]){ 11 if(num[mid] < num[mid-1]) 12 return binary_sarch(num, begin, mid-1); 13 return binary_sarch(num, mid+1, end); 14 } 15 if(num[mid] < num[begin]) 16 return binary_sarch(num, begin, mid-1); 17 else 18 return binary_sarch(num, mid+1, end); 19 } int findpeakelement(const vector<int> &num) { 22 int n = num.size(); 23 if(n <= 1) return n-1; 24 if(ispeak(num, 0)) return 0; 25 if(ispeak(num, n-1)) return n - 1; 26 return binary_sarch(num, 1, n-2); 27 }

130 第 11 章分治 11.1 基本概念 分治算法的基本思想是将一个规模为 N 的问题分解为 K 个规模较小的子问题, 这些子问题相互独立且与原问题性质相同 求出子问题的解, 就可得到原问题的解. 分治法解题的一般步骤 : (1) 分解, 将要解决的问题划分成若干规模较小的同类问题 ; (2) 求解, 当子问题划分得足够小时, 用较简单的方法解决 ; (3) 合并, 按原问题的要求, 将子问题的解逐层合并构成原问题的解 当我们求解某些问题时, 由于这些问题要处理的数据相当多, 或求解过程相当复杂, 使得直接求解法在时间上相当长, 或者根本无法直接求出 对于这类问题, 我们往往先把它分解成几个子问题, 找到求出这几个子问题的解法后, 再找到合适的方法, 把它们组合成求整个问题的解法 如果这些子问题还较大, 难以解决, 可以再把它们分成几个更小的子问题, 以此类推, 直至可以直接求出解为止 这就是分治策略的基本思想 121

131 11.2 经典问题 经典问题 分治法有很多经典问题, 比如归并排序, 最近点对问题, 最大连续子数组和, 网格覆盖问题等

132 11.3 相关问题 相关问题 TODO

133 第 12 章搜索 12.1 基本概念 搜索, 通常是求解问题的一种非常常用的方法, 常见的搜索方法有 DFS, BFS, A* 搜索等等. 有时候大家会混淆搜索和回溯, 特别是 DFS 和回溯的区别, 我的理解就是回溯是一种算法, 而搜索是一种方法. 124

134 12.2 宽度优先搜索 宽度优先搜索, BFS 通常适用于寻找最短路径问题, 因为 BFS 是一步一步的向结果节点靠近的, 所以一旦搜到那个此搜索路径必然是最短路径之一 Word Ladder Given two words (beginword and endword), and a dictionary, find the length of shortest transformation sequence from beginword to endword, such that: 1. Only one letter can be changed at a time 2. Each intermediate word must exist in the dictionary (leetcode 127) 举例 : Given: start = hit end = cog dict = ( hot, dot, dog, lot, log ) As one shortest transformation is hit - hot - dot - dog - cog, return its length 5. Note: Return 0 if there is no such transformation sequence. All words have the same length. All words contain only lowercase alphabetic characters. BFS : 时间复杂度 O(???), 空间复杂度 O(???) 典型的 BFS 问题, 使用队列, 一层一层的找到最优解. 1 int ladderlength(string start, string end, unordered_set<string> &dict) { 2 unordered_set < string > used; 3 queue<string> q; 4 q.push(start); 5 dict.insert(start); 6 dict.insert(end); 7 used.insert(start); 8 int deep = 0, last = 1; 9 while(!q.empty()){ 10 start = q.front(); 11 q.pop(); 12 if(start.compare(end) == 0) return deep + 1; 13 string tmp = start; 14 for(int i = 0; i < start.size(); i++){ 15 start = tmp; 16 for(int j = a ; j <= z ; j++){ 17 start[i] = j; 18 if(dict.count(start) &&!used.count(start)){ 19 used.insert(start); 20 q.push(start); 21 } 22 } 23 } 24 last--;

135 12.2 宽度优先搜索 if(last == 0){ 26 last = q.size(); 27 deep ++; 28 } 29 } 30 return 0; 31 } Word Ladder II Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that: Only one letter can be changed at a time Each intermediate word must exist in the dictionary (leetcode 126) 举例 : Given: start = hit end = cog dict = ( hot, dot, dog, lot, log ) Return ( ( hit, hot, dot, dog, cog ), ( hit, hot, lot, log, cog ) ) Note: All words have the same length. All words contain only lowercase alphabetic characters. BFS : 时间复杂度 O(???), 空间复杂度 O(???) 由于最短路径才可以是最优解, 所以不推荐使用 DFS, 而是使用 BFS 实现回溯. 这道题需要注意不要访问重复点, 还有时间空间要把握好, 稍微不慎就会 TLE 或者 MLE 1 void gentrack(vector<pair<string, int> > &tree, int pos, vector<string> &track){ 2 if(pos == 0){ 3 track.push_back(tree[0].first); 4 return; 5 } 6 gentrack(tree, tree[pos].second, track); 7 track.push_back(tree[pos].first); 8 } 9 10 /* 多一丁点计算都会超时多一丁点空间都会超空间,...*/ 11 vector<vector<string> > findladders(string start, string end, 12 unordered_set<string> &dict){ 13 dict.insert(start); 14 vector<vector<string> > result; 15 vector<pair<string, int> > tree; 16 unordered_map<string, int> used; 17 used[start] = 0;

136 12.2 宽度优先搜索 tree.push_back(make_pair(start, -1)); 19 int last = 1, pre = -1, pos = 0; 20 bool found = false; 21 string cur, tmp; 22 while(pos < last){ 23 cur = tree[pos].first; 24 tmp = cur; 25 for(int i = 0; i < cur.size(); i++){ 26 cur = tmp; 27 for(int j = a ; j <= z ; j++){ 28 cur[i] = j; 29 if(cur.compare(end) == 0){ 30 found = true; 31 vector<string> track; 32 gentrack(tree, pos, track); 33 track.push_back(end); 34 result.push_back(track); 35 continue; 36 } 37 if(dict.count(cur) == 1 && (used.count(cur) == 0 used [cur] >= pre )){ 38 used[cur] = pos; 39 tree.push_back(make_pair(cur, pos)); 40 } 41 } 42 } 43 pos ++; 44 if(!found && pos == last){ 45 pre = last; 46 last = tree.size(); 47 } 48 } 49 return result; 50 } Number of Islands Given a 2d grid map of 1 s (land) and 0 s (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. (leetcode 200) 举例 : Example 1: Answer: 1 Example 2:

137 12.2 宽度优先搜索 Answer: 3 BFS : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 这题就是通过 BFS 找到一个个的图连通分量, 其实也可以使用 DFS 进行搜索. 另外, 需要学习的就是这种对付矩阵四个方向问题, 可以在主函数每个方向都调用 visit 函数, 然后在 visit 函数中判断是否合法, 这比直接判断合法要简洁清楚很多. 1 void visit(vector<vector<char> > &grid, vector<vector<bool> > &visited, 2 queue<pair<int, int> > &q, int n, int m, int i, int j){ 3 if(i < 0 j < 0 i > n-1 j > m-1 4 grid[i][j] == 0 visited[i][j]) return; 5 visited[i][j] = true; 6 q.push(make_pair(i,j)); 7 } 8 9 void bfs(vector<vector<char> > &grid, vector<vector<bool> > &visited, 10 int n, int m, int i, int j){ 11 queue<pair<int, int> > q; 12 q.push(make_pair(i, j)); 13 pair<int,int> cur; 14 visited[i][j] = true; 15 while(!q.empty()){ 16 cur = q.front(); 17 q.pop(); 18 i = cur.first; 19 j = cur.second; 20 visit(grid, visited, q, n, m, i+1, j); 21 visit(grid, visited, q, n, m, i-1, j); 22 visit(grid, visited, q, n, m, i, j-1); 23 visit(grid, visited, q, n, m, i, j+1); 24 } 25 } int numislands(vector<vector<char> >& grid) { 28 int n = grid.size(); 29 if(n == 0) return 0; 30 int m = grid[0].size(); 31 vector<vector<bool> > visited(n, vector<bool>(m, false)); 32 int count = 0; 33 for(int i = 0; i < n; i++) 34 for(int j = 0; j < m; j++) 35 if(!visited[i][j] && grid[i][j] == 1 ){ 36 count ++; 37 bfs(grid, visited, n, m, i, j); 38 } 39 return count; 40 } Surrounded Regions Given a 2D board containing X and O, capture all regions surrounded by X.

138 12.2 宽度优先搜索 129 A region is captured by flipping all O s into X s in that surrounded region. (leetcode 130) 举例 : X X X X X O O X X X O X X O X X After running your function, the board should be: X X X X X X X X X X X X X O X X BFS : 时间复杂度 O(n*m), 空间复杂度 O(1) 这题可以用 BFS/DFS 进行搜索, 但是这里有一个 trick: 我们知道 O 要想存活必须能够扩展到边界, 否则就肯定被围, 所以我们与其随便找个 O 点开始 BFS 不如从边界某个 O 开始 BFS, 因为你随便一点 BFS 时候在你搜索过程中你还不清楚到底这块会不会被围 ; 但是你从边界的 O 开始 BFS 那么只要与它连通的肯定是活的, 就可以通过这个 trcik 节省空间开销. 1 void visit(vector<vector<char> >& board, queue<pair<int, int> > &search, 2 int n, int m, int i, int j){ 3 if(i < 0 i > n-1 j < 0 j > m-1 board[i][j]!= O ) return; 4 search.push(make_pair(i, j)); 5 board[i][j] = - ; 6 } 7 8 void bfs(vector<vector<char> > &board, int n, int m, int i, int j){ 9 if(board[i][j]!= O ) return; 10 queue<pair<int,int> > search; 11 pair<int, int> cur; 12 search.push(make_pair(i, j)); 13 board[i][j] = - ; 14 while(!search.empty()){ 15 cur = search.front(); 16 search.pop(); 17 i = cur.first; 18 j = cur.second; 19 visit(board, search, n, m, i-1, j); 20 visit(board, search, n, m, i+1, j); 21 visit(board, search, n, m, i, j-1); 22 visit(board, search, n, m, i, j+1); 23 } 24 } void solve(vector<vector<char> > &board) { 27 int n = board.size(); 28 if(n == 0) return; 29 int m = board[0].size(); 30 // 从四个边开始搜那么找到的必然是活的, 31 for(int i = 0; i < n; i++){ 32 bfs(board, n, m, i, 0); 33 bfs(board, n, m, i, m-1); 34 } 35 for(int j = 0; j < m; j++){

139 12.2 宽度优先搜索 bfs(board, n, m, 0, j); 37 bfs(board, n, m, n-1, j); 38 } 39 for(int i = 0; i < n; i++) 40 for(int j = 0; j < m; j++){ 41 if(board[i][j] == O ) board[i][j] = X ; 42 if(board[i][j] == - ) board[i][j] = O ; 43 } 44 } Binary Tree Right Side View Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom. (leetcode 199) 举例 : Given the following binary tree, 1 / \ 2 3 \ \ 5 4 You should return (1, 3, 4). BFS : 时间复杂度 O(n), 空间复杂度 O(w) 最普通的宽度优先搜索之队列法. 1 vector<int> rightsideview(treenode* root) { 2 vector<int> result; 3 if(!root) return result; 4 queue<treenode*> cur, next; 5 cur.push(root); 6 TreeNode* pos; 7 while(!cur.empty()){ 8 pos = cur.front(); 9 cur.pop(); 10 if(pos->left) next.push(pos->left); 11 if(pos->right) next.push(pos->right); 12 if(cur.empty()){ 13 result.push_back(pos->val); 14 next.swap(cur); 15 } 16 } 17 return result; 18 }

140 12.3 深度优先问题 深度优先问题 深度优先搜索在树和回溯那几章已经大量使用, 这里就不做专门讨论.

141 12.4 A* 搜索 A* 搜索 TODO

142 第 13 章回溯 13.1 基本概念 回溯算法实际上一个类似枚举的搜索尝试过程, 主要是在搜索尝试过程中寻找问题的解, 当发现已不满足求解条件时, 就 回溯 返回, 尝试别的路径 回溯法是一种选优搜索法, 按选优条件向前搜索, 以达到目标 但当探索到某一步时, 发现原先选择并不优或达不到目标, 就退回一步重新选择, 这种走不通就退回再走的技术为回溯法, 而满足回溯条件的某个状态的点称为 回溯点 在包含问题的所有解的解空间树中, 按照深度优先搜索的策略, 从根结点出发深度探索解空间树 当探索到某一结点时, 要先判断该结点是否包含问题的解, 如果包含, 就从该结点出发继续探索下去, 如果该结点不包含问题的解, 则逐层向其祖先结点回溯 ( 其实回溯法就是对隐式图的深度优先搜索算法 ) 有时候需要对搜索空间树进行剪枝, 以加快回溯的速度. 133

143 13.2 经典问题 经典问题 回溯法虽然有着 通用解法 的美称, 但是一般来说很多问题我们还是优先考虑更优的解法, 比如动态规划等方法, 但是有些问题就不得不使用回溯法求解了. 这里比较著名的就有 N 皇后问题等 N-Queens The n-queens puzzle is the problem of placing n queens on an n n chessboard such that no two queens attack each other. Given an integer n, return all distinct solutions to the n-queens puzzle. Each solution contains a distinct board configuration of the n-queens placement, where Q and. both indicate a queen and an empty space respectively.(leetcode 51) 回溯 : 时间复杂度 O(n!), 空间复杂度 O(n 2 ) 其实时间复杂度应该远小于 O(n!), 这里只是确定一个上界. 1 bool issafe(vector<pair<int, int> > &queens, int i, int j){ 2 for(int k = 0; k < queens.size(); k++){ 3 int x = queens[k].first, y = queens[k].second; 4 if(x == i y == j abs(i - x) == abs(j - y)) 5 return false; 6 } 7 return true; 8 } 9 10 void backtrack(vector<vector<string> > &result, vector<pair<int, int> > &queens, 11 vector<string> &track, int row, int row_num){ 12 if(row == row_num){ 13 result.push_back(track); 14 return; 15 } 16 string lines(row_num,. ); 17 for(int i = 0; i < row_num; i++){ 18 if(issafe(queens, row, i)){ 19 queens.push_back(make_pair(row, i)); 20 lines[i] = Q ; 21 track.push_back(lines); 22 backtrack(result, queens, track, row+1, row_num); 23 track.pop_back(); 24 lines[i] =. ; 25 queens.pop_back(); 26 } 27 } 28 } vector<vector<string> > solvenqueens(int n) { 31 vector<vector<string> > result; 32 if(n <= 0) return result; 33 vector<pair<int, int> > queens; 34 vector<string> track; 35 backtrack(result, queens, track, 0, n); 36 return result; 37 }

144 13.2 经典问题 N-Queens II Follow up for N-Queens problem. Now, instead outputting board configurations, return the total number of distinct solutions.(leetcode 52) 回溯 : 时间复杂度 O(n!), 空间复杂度 O(n) 同样时间复杂度应该远小于 O(n!) 1 bool issafe(vector<pair<int, int> > &queens, int i, int j){ 2 for(int k = 0; k < queens.size(); k++){ 3 int x = queens[k].first, y = queens[k].second; 4 if(x == i y == j abs(i - x) == abs(j - y)) 5 return false; 6 } 7 return true; 8 } 9 10 void backtrack(int &result, vector<pair<int, int> > &queens, 11 int row, int row_num){ 12 if(row == row_num){ 13 result ++; 14 return; 15 } 16 for(int i = 0; i < row_num; i++){ 17 if(issafe(queens, row, i)){ 18 queens.push_back(make_pair(row, i)); 19 backtrack(result, queens, row+1, row_num); 20 queens.pop_back(); 21 } 22 } 23 } int totalnqueens(int n) { 26 if(n <= 0) return 0; 27 vector<pair<int, int> > queens; 28 int result = 0; 29 backtrack(result, queens, 0, n); 30 return result; 31 }

145 13.3 相关问题 相关问题 回溯法可以解决很多需要枚举才能确定解的问题, 一般来说会有 a 1,a 2,...,a n 的值需要确定, 且这些 a i 还要满足某种约束. 我们采用的办法是 : 从 a 1 开始分别枚举它的所有可能值, 然后采用 DFS 的做法, 依次确定 a 2,..., 如果确定到 a n, 那么这组枚举解是合理解, 就放入到搜索结果中, 如果枚举到某个 a i 发现它已经没有可用解时候, 就需要回溯. 另外, 我的编码风格是使用 result 存放最终解, 而 trace 保存一路 DFS 下来的状态信息, 每次进入一个新节点时候 trace 要压入新节点的状态, 每次离开该节点时候就需要 pop 刚才压入的状态 Letter Combinations of a Phone Number Given a digit string, return all possible letter combinations that the number could represent. A mapping of digit to letters (just like on the telephone buttons) is given below. (leetcode 17) 举例 : Input:Digit string 23 Output: [ ad, ae, af, bd, be, bf, cd, ce, cf ]. Note: Although the above answer is in lexicographical order, your answer could be in any order you want. 回溯 : 时间复杂度 O(3 n ), 空间复杂度 O(n) 1 string table[] = {" ","", "abc", "def", "ghi", "jkl", "mno", "pqrs", " tuv", "wxyz"}; 2 3 void dfs(vector<string> &result, string& digits, string& trace, int pos) { 4 if(pos == digits.size()){ 5 result.push_back(trace); 6 return; 7 } 8 string& all = table[digits[pos] - 0 ]; 9 for(int i = 0; i < all.size(); i++){ 10 trace.push_back(all[i]); 11 dfs(result, digits, trace, pos+1); 12 trace.erase(trace.size() - 1);

146 13.3 相关问题 } 14 } vector < string > lettercombinations( string digits) { 17 vector<string> result; 18 int n = digits.size(); 19 if(n == 0) return result; 20 string trace; 21 dfs(result, digits, trace, 0); 22 return result; 23 } Generate Parentheses Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.(leetcode 22) 举例 : Given n = 3, a solution set is: ((())), (()()), (())(), ()(()), ()()() 回溯 : 时间复杂度 O(2 n ), 空间复杂度 O(n) 这个时间复杂度也是会远低于 O(2 n ) 1 void dfs(vector<string> &result, int n, string& trace, int left){ 2 if(left == -1 (n == 0 && left!= 0)) return; 3 if(n == 0){ 4 result.push_back(trace); 5 return ; 6 } 7 trace.push_back( ( ); 8 dfs(result, n-1, trace, left+1); 9 trace[trace.size()-1] = ) ; 10 dfs(result, n-1, trace, left - 1); 11 trace.erase(trace.size()-1); 12 } vector<string> generateparenthesis(int n) { 15 vector<string> result; 16 string trace; 17 int left = 0; 18 dfs(result, 2*n, trace, left); 19 return result; 20 } Sudoku Solver Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cells are indicated by the character.. You may assume that there will be only one unique solution. (leetcode 37)

147 13.3 相关问题 138 举例 : Asudokupuzzle... 回溯 : 时间复杂度 O(9!8!7!...1!), 空间复杂度 O(1) 时间复杂度也是远远没有 O(9!8!7!...1!) 这么多 1 unordered_set <char > row[9]; 2 unordered_set <char > col[9]; 3 unordered_set <char > area[9]; 4 5 bool dfs(vector<vector<char> > &board, int i, int j){ 6 if(i == 9) return true; 7 if(board[i][j]!=. ){ 8 i = j == 8? i+1 : i; 9 j = j == 8? 0 : j+1; 10 return dfs(board, i, j); 11 }else{ 12 for(int k = 1; k <= 9; k++){ 13 char cur = 0 + k; 14 if(!row[i].count(cur) &&!col[j].count(cur) 15 &&!area[(i/3)*3 + j/3].count(cur)){ 16 row[i].insert(cur); 17 col[j].insert(cur); 18 area[(i/3)*3 + j/3].insert(cur); 19 board[i][j] = cur; 20 i = j == 8? i+1 : i; 21 j = j == 8? 0 : j+1; 22 if(dfs(board, i, j)) return true; 23 i = j == 0? i-1 : i; 24 j = j == 0? 8 : j-1; 25 row[i].erase(cur); 26 col[j].erase(cur); 27 area[(i/3)*3 + j/3].erase(cur); 28 } 29 } 30 board[i][j] =. ; 31 return false;

148 13.3 相关问题 } 33 } void solvesudoku(vector<vector<char> > &board) { 36 for(int i = 0; i < 9; i++){ 37 row[i].clear(); 38 col[i].clear(); 39 area[i].clear(); 40 } 41 for(int i = 0; i < 9; i++) 42 for(int j = 0; j < 9; j++){ 43 if(board[i][j]!=. ){ 44 row[i].insert(board[i][j]); 45 col[j].insert(board[i][j]); 46 area[(i/3)*3+j/3].insert(board[i][j]); 47 } 48 } 49 dfs(board, 0, 0); 50 } Combination Sum Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T. The same repeated number may be chosen from C unlimited number of times. (leetcode 39) 举例 : Given candidate set 2,3,6,7 and target 7, A solution set is: (7) (2, 2, 3) Note: All numbers (including target) will be positive integers. Elements in a combination (a1, a2,..., ak) must be in non-descending order. (ie, a1 a2... ak). The solution set must not contain duplicate combinations. 回溯 : 时间复杂度 O(2 n ), 空间复杂度 O(n) 这题就是对于每个值包含还是不包含两种选择, 然后进行回溯算法. 这里需要注意的就是遇到一个值有多个的情况, 这里因为每个值可以用无数次, 所以就可以直接把原数组数据去重就可以了, 具体办法可以每次枚举完 a i 后前向枚举下一个第一个不等于 a i 的那个. 1 void backtrack(vector<int> &num, int target, int pos, 2 vector<vector<int> > &result, vector<int> &track){ 3 if(target == 0){ 4 result.push_back(track); 5 return; 6 } 7 if(pos == -1) return; 8 if(num[pos] <= target){ 9 track.push_back(num[pos]); 10 backtrack(num, target - num[pos], pos, result, track); 11 track.pop_back(); 12 }

149 13.3 相关问题 pos--; 14 while(pos >= 0 && num[pos] == num[pos+1]) pos--; 15 if(pos >= 0){ 16 backtrack(num, target, pos, result, track); 17 } 18 } vector<vector<int> > combinationsum(vector<int> &nums, int target) { 21 sort(nums.begin(), nums.end(), greater<int>()); 22 vector<vector<int> > result; 23 vector<int> track; 24 backtrack(nums, target, nums.size()-1, result, track); 25 return result; 26 } Combination Sum II Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T. Each number in C may only be used once in the combination. (leetcode 40) Note: All numbers (including target) will be positive integers. Elements in a combination (a1, a2,..., ak) must be in non-descending order. (ie, a1 a2... ak). The solution set must not contain duplicate combinations. 举例 : For example, given candidate set 10,1,2,7,6,1,5 and target 8, A solution set is: (1, 7) (1, 2, 5) (2, 6) (1, 1, 6) 回溯 : 时间复杂度 O(2 n ), 空间复杂度 O(n) 这题就是对于每个值包含还是不包含两种选择, 然后进行回溯算法. 这里需要注意的就是遇到一个值有多个的情况, 这里通常最简单的处理办法就是进行浓缩, 把原数组改成一个个点对, 例如 (1,1,1,2,3,3) 改成 ((1,3),(2,1),(3,2)) 这样再每次选择时候消耗一下记数, 这样就不会出现重复的结果了. 1 void backtrack(vector<int> &key, vector<int> &count, int target, 2 int pos, vector<vector<int> > &result, vector<int> &track){ 3 if(target == 0){ 4 result.push_back(track); 5 return; 6 } 7 if(pos == -1) return; 8 if(count[pos] > 0 && key[pos] <= target){ 9 track.push_back(key[pos]); 10 count[pos]--; 11 backtrack(key, count, target - key[pos], pos, result, track); 12 count[pos]++;

150 13.3 相关问题 track.pop_back(); 14 } 15 backtrack(key, count, target, pos-1, result, track); 16 } vector<vector<int> > combinationsum2(vector<int> &num, int target) { 19 sort(num.begin(), num.end(), greater<int>()); 20 vector<int> key, count, track; 21 vector<vector<int> > result; 22 if(num.size() == 0) return result; 23 int last = 0; 24 for(int i = 1; i < num.size(); i++){ 25 if(num[i]!= num[i-1]){ 26 key.push_back(num[last]); 27 count.push_back(i - last); 28 last = i; 29 } 30 } 31 key.push_back(num[last]); 32 count.push_back(num.size() - last); 33 backtrack(key, count, target, key.size() - 1, result, track); 34 return result; 35 } Permutations Given a collection of numbers, return all possible permutations.(leetcode 46) 举例 : (1,2,3) have the following permutations: (1,2,3), (1,3,2), (2,1,3), (2,3,1), (3,1,2), and (3,2,1). 排序 : 时间复杂度 O(n!), 空间复杂度 O(n) 这题本应该使用回溯法求解, 特别是对于没有重复元素情况可以非常简单的使用回溯法求解, 但是这道题又是经典的组合数学 我们把这些数的组合看成一个数, 例如 (1,2,3) 看成 123,(3,2,1) 看成 321, 那么我们可以从最小的数开始逐渐增大这个数直到算到最大的数, 那么所有的组合也就求出来了. 那么怎么根据现在的数求出下一个更大一点的数的? 假设我们现在的数是 a 1,a 2,...a n, 我们找该序列最后一个极大点, 设为 a k, 我们知道 a k 1 <a k, a k >a k+1 >...>a n, 另外设 a m 是 a k,...,a n 中大于 a k 1 的最小的那个, 那么下一个数就是 a 1,a 2,...a m,a k,a k+1,...,a m 1,a k 1,a m+1,...,a n 1 vector<vector<int> > permute(vector<int> &num) { 2 vector<int> data(num); 3 sort(data.begin(), data.end()); 4 vector<vector<int> > result; 5 while(1){ 6 int peak = data.size() - 1; 7 while(peak > 0 && data[peak] <= data[peak-1]) peak--; 8 result.push_back(data); 9 if(peak == 0) break; 10 int before_peak = peak - 1; 11 vector<int>::iterator next_peak = lower_bound(data.begin() + peak, data.end(), 12 data[before_peak], greater<int>()); 13 next_peak--; 14 swap(data[before_peak], *next_peak); 15 reverse(data.begin() + peak, data.end()); 16 }

151 13.3 相关问题 return result; 18 } Permutations II Given a collection of numbers that might contain duplicates, return all possible unique permutations.(leetcode 47) 举例 : (1,1,2) have the following unique permutations: (1,1,2), (1,2,1), and (2,1,1). 排序 : 时间复杂度 O(n!), 空间复杂度 O(n) 这题同样还是采用转为大数处理, 这里就可以看到, 如果采用回溯法, 那么还要想办法去掉由于有重复元素带来的问题 ( 压缩原数组变成 ( 元素, 记数 ) 对 ). 而这个方法则不需要, 是不是很飘逸呢. 1 vector<vector<int> > permuteunique(vector<int>& nums) { 2 vector<int> data(nums); 3 sort(data.begin(), data.end()); 4 vector<vector<int> > result; 5 while(1){ 6 int peak = data.size() - 1; 7 while(peak > 0 && data[peak] <= data[peak-1]) peak--; 8 result.push_back(data); 9 if(peak == 0) break; 10 int before_peak = peak - 1; 11 vector<int>::iterator next_peak = lower_bound(data.begin() + peak, data.end(), 12 data[before_peak], greater<int>()); 13 next_peak--; 14 swap(data[before_peak], *next_peak); 15 reverse(data.begin() + peak, data.end()); 16 } 17 return result; 18 } Permutation Sequence The set [1,2,3,...,n] contains a total of n! unique permutations. By listing and labeling all of the permutations in order, We get the following sequence (ie, for n = 3): Given n and k, return the kth permutation sequence. (leetcode 60) Note: Given n will be between 1 and 9 inclusive.

152 13.3 相关问题 143 枚举 : 时间复杂度 O(n), 空间复杂度 O(n) 这题其实就是能计算出每种长度的组合有多少个, 然后一个个的数就行了. 1 string getpermutation(int n, int k) { 2 vector<int> num, count(n+1, 1); 3 for(int i = 1; i <= n; i++) 4 count[i] = i * count[i-1]; 5 int pos = n; 6 vector<bool> use(n+1, true); 7 while(k > 0){ 8 int i = 0, c = 0; 9 for(i = 1; i <= n; i++){ 10 if(use[i]) c++; 11 if( k <= count[pos-1] * c) { 12 num.push_back(i); 13 use[i] = false; 14 k = k - count[pos-1] * (k == count[pos-1] * c? c : c-1); 15 break; 16 } 17 } 18 pos--; 19 } 20 for(int i = n; i >= 1; i--) 21 if(use[i]) num.push_back(i); string result(n, 0 ); 24 for(int i = 0; i < n; i++) 25 result[i] += num[i]; 26 return result; 27 } Combinations Given two integers n and k, return all possible combinations of k numbers out of 1... n. (leetcode 77) 举例 : If n = 4 and k = 2, a solution is: ( (2,4), (3,4), (2,3), (1,2), (1,3), (1,4), ) 回溯 : 时间复杂度 O(2 n ), 空间复杂度 O(n) 从 1,2,3,...,n 每个枚举有还是没有这两种可能. 1 void backtrack(vector<vector<int> > &result, vector<int> &track, 2 int count, int k, int pos, int n){ 3 if(count == k){ 4 result.push_back(track); 5 return; 6 } 7 if(pos >= n k <= 0 n <= 0) return; 8 track.push_back(pos+1); 9 backtrack(result, track, count+1, k, pos+1, n); 10 track.pop_back(); 11 backtrack(result, track, count, k, pos+1, n);

153 13.3 相关问题 } vector<vector<int> > combine(int n, int k) { 15 vector<vector<int> > result; 16 vector<int> track; 17 backtrack(result, track, 0, k, 0, n); 18 return result; 19 } Subsets Given a set of distinct integers, nums, return all possible subsets. (leetcode 78) 举例 : If nums = (1,2,3), a solution is: ( (3), (1), (2), (1,2,3), (1,3), (2,3), (1,2), () ) Note: Elements in a subset must be in non-descending order. The solution set must not contain duplicate subsets. 回溯 : 时间复杂度 O(2 n ), 空间复杂度 O(n) 其实这是一道枚举题目, 枚举每个元素包不包含进来两种情况即可. 它是没有任何约束条件的回溯, 一条道走到黑都是解. 1 void backtrack(vector<vector<int> > &result, vector<int> &track, 2 vector<int> &S, int pos, int n){ 3 if(pos == n){ 4 result.push_back(track); 5 return; 6 } 7 track.push_back(s[pos]); 8 backtrack(result, track, S, pos+1, n); 9 track.pop_back(); 10 backtrack(result, track, S, pos+1, n); 11 } vector<vector<int> > subsets(vector<int> &S) { 14 sort(s.begin(), S.end()); 15 int n = S.size(); 16 vector<vector<int> > result; 17 vector<int> track; 18 backtrack(result, track, S, 0, n); 19 return result;

154 13.3 相关问题 } Subsets II Given a collection of integers that might contain duplicates, nums, return all possible subsets. (leetcode 90) 举例 : If nums = (1,2,2), a solution is: ( (2), (1), (1,2,2), (2,2), (1,2), () ) Note: Elements in a subset must be in non-descending order. The solution set must not contain duplicate subsets. 回溯 : 时间复杂度 O(2 n ), 空间复杂度 O(n) 这题唯一的区别就是含有重复元素, 对于这种问题还是那个老办法, 先统计重复元素, 制作成 ( 元素, 记数 ) 对, 然后再回溯搜索. 1 void backtrack(vector<vector<int> > &result, vector<int> &track, vector< int> & data, 2 vector<int> &count, int pos, int n){ 3 if(pos == n){ 4 result.push_back(track); 5 return; 6 } 7 for(int i = 0; i < count[pos]; i++){ 8 track.push_back(data[pos]); 9 backtrack(result, track, data, count, pos+1, n); 10 } 11 for(int i = 0; i < count[pos]; i++){ 12 track.pop_back(); 13 } 14 backtrack(result, track, data, count, pos+1, n); 15 } vector<vector<int> > subsetswithdup(vector<int> &S) { 18 sort(s.begin(), S.end()); 19 vector<int> data, count; 20 int last = 0; 21 for(int i = 1; i < S.size(); i++){ 22 if(s[i]!= S[i-1]){ 23 data.push_back(s[last]); 24 count.push_back(i - last); 25 last = i; 26 } 27 }

155 13.3 相关问题 data.push_back(s[last]); 29 count.push_back(s.size() - last); 30 vector<vector<int> > result; 31 vector<int> track; 32 backtrack(result, track, data, count, 0, data.size()); 33 return result; 34 } Word Search Given a 2D board and a word, find if the word exists in the grid. The word can be constructed from letters of sequentially adjacent cell, where adjacent cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once. (leetcode 79) 举例 : Given board = ( ( ABCE ), ( SFCS ), ( ADEE ) ) word = ABCCED, - returns true, word = SEE, - returns true, word = ABCB, - returns false. 回溯 : 时间复杂度 (n m 4 k ), 空间复杂度 O(n*m) 这里从每个点开始出发搜索, 不停的向四个方向搜索并回溯. 1 bool backtrack(vector<vector<char> > &board, vector<vector<bool> > &used, string &word, 2 int i, int j, int n, int m, int pos, int len){ 3 if(pos == len) return true; 4 if(i < 0 i >= n j < 0 j >= m used[i][j]) return false ; 5 if(board[i][j] == word[pos]){ 6 used[i][j] = true; 7 if(backtrack(board, used, word, i+1, j, n, m, pos+1, len)) return true; 8 if(backtrack(board, used, word, i-1, j, n, m, pos+1, len)) return true; 9 if(backtrack(board, used, word, i, j+1, n, m, pos+1, len)) return true; 10 if(backtrack(board, used, word, i, j-1, n, m, pos+1, len)) return true; 11 used[i][j] = false; 12 } 13 if(pos!= 0) return false; 14 if(backtrack(board, used, word, i, j+1, n, m, pos, len)) return true; 15 if(j == m-1 && backtrack(board, used, word, i+1, 0, n, m, pos, len)) return true;

156 13.3 相关问题 return false; 17 } bool exist(vector<vector<char> > &board, string word) { 20 int n = board.size(), len = word.size(); 21 if(n == 0 len == 0) return false; 22 int m = board[0].size(); 23 vector<vector<bool> > used(n, vector<bool>(m, false)); 24 return backtrack(board, used, word, 0, 0, n, m, 0, len); 25 } Gray Code The gray code is a binary numeral system where two successive values differ in only one bit. Given a non-negative integer n representing the total number of bits in the code, print the sequence of gray code. A gray code sequence must begin with 0. (leetcode 89) 举例 : Given n = 2, return [0,1,3,2]. Its gray code sequence is: Note: For a given n, a gray code sequence is not uniquely defined. For example, [0,2,3,1] is also a valid gray code sequence according to the above definition. For now, the judge is able to judge based on one instance of gray code sequence. Sorry about that. 回溯 : 时间复杂度 O(2 n ), 空间复杂度 O(2 n ) 这道题其实和回溯没多大关系, 我的解法就是从第 1 位开始确定数据, 接着确定第二位,... 第三位... 第 k 位的数据就是第 k-1 位数据集合并上第 k-1 位数据集合的反序集合. 从这个角度上看也算是回溯吧. 1 vector<int> graycode(int n) { 2 vector<int> result; 3 if(n < 0 n > 31) return result; 4 if(n == 0) return vector<int>(1, 0); 5 vector<int> last{0, 1}; 6 result = last; 7 for(int i = 1; i < n; i++){ 8 last = result; 9 reverse(last.begin(), last.end()); 10 for(int j = 0; j < last.size(); j++){ 11 last[j] = (0x1 << i); 12 result.push_back(last[j]); 13 } 14 } 15 return result; 16 }

157 13.3 相关问题 Restore IP Addresses Given a string containing only digits, restore it by returning all possible valid IP address combinations.(leetcode 93) 举例 : Given , return [ , ]. (Order does not matter) 回溯 : 时间复杂度 O(C 3 11), 空间复杂度 O(11) 同样使用回溯, 每一步选择 IP 地址的新的一个字段的值, 这个字段可以由 1,2 或者 3 个数字组成. 1 int getcon(string &s, int pos, int len){ 2 if(s[pos] == 0 pos+1 == len) return 1; 3 if(pos+2 == len) return 2; 4 string substr = s.substr(pos, 3); 5 if(substr.compare("256") < 0) return 3; 6 return 2; 7 } 8 9 void backtrack(vector<string> &result, string &track, string &s, 10 int pos, int len, int count, int n){ 11 if(count == n && pos == len){ 12 result.push_back(track); 13 return; 14 } 15 if(count == n pos == len) return; 16 int con = getcon(s, pos, len); 17 string tmp = track; 18 for(int i = 1; i <= con; i++){ 19 track = tmp + (tmp.empty()? "" : ".") + s.substr(pos, i); 20 backtrack(result, track, s, pos+i, len, count+1, n); 21 } 22 track = tmp; 23 } vector < string > restoreipaddresses( string s) { 26 int n = s.length(); 27 vector<string> result; 28 string track; 29 backtrack(result, track, s, 0, n, 0, 4); 30 return result; 31 } Palindrome Partitioning Given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of s. (leetcode 131) 举例 : Given s = aab, Return

158 13.3 相关问题 149 ( ( aa, b ), ( a, a, b ) ) 回溯 : 时间复杂度, 空间复杂度 O 首先预处理字符串, 找到所有回文区间, 然后从位置 0 开始, 每个回文区间都尝试一下, 采用回溯法搜索结果. 1 void backtrack(vector<vector<string> > &result, vector<string> &track, 2 vector<vector<bool> > &pal, string &s, int pos, int n){ 3 if(pos == n){ 4 result.push_back(track); 5 return; 6 } 7 for(int j = pos; j < n; j++){ 8 if(pal[pos][j]){ 9 track.push_back(s.substr(pos, j - pos + 1)); 10 backtrack(result, track, pal, s, j+1, n); 11 track.pop_back(); 12 } 13 } 14 } vector<vector<string> > partition(string s) { 17 int n = s.size(); 18 vector<vector<string> > result; 19 vector<string> track; 20 vector<vector<bool> > pal(n, vector<bool>(n, false)); 21 for(int i = 0; i < n; i++){ 22 for(int j = 0; i - j >= 0 && i + j < n; j++){ 23 if(s[i-j]!= s[i+j]) break; 24 pal[i-j][i+j] = true; 25 } 26 } 27 for(int i = 1; i < n; i++){ 28 for(int j = 1; i - j >= 0 && i + j <= n; j++){ 29 if(s[i-j]!= s[i+j-1]) break; 30 pal[i-j][i+j-1] = true; 31 } 32 } 33 backtrack(result, track, pal, s, 0, n); 34 return result; 35 }

159 第 14 章贪心 14.1 基本概念 贪心法, 又称贪心算法, 是一种在每一步选择中都采取在当前状态下最好或最优 ( 即最有利 ) 的选择, 从而希望导致结果是最好或最优的算法 [1] 比如在旅行推销员问题中, 如果旅行员每次都选择最近的城市, 那这就是一种贪心算法 贪心算法在有最优子结构的问题中尤为有效 最优子结构的意思是局部最优解能决定全局最优解 简单地说, 问题能够分解成子问题来解决, 子问题的最优解能递推到最终问题的最优解 贪心算法与动态规划的不同在于它每对每个子问题的解决方案都做出选择, 不能回退 动态规划则会保存以前的运算结果, 并根据以前的结果对当前进行选择, 有回退功能 贪心法可以解决一些最优化问题, 如 : 求图中的最小生成树, 求哈夫曼编码,Dijkstra 算法... 对于其他问题, 贪心法一般不能得到我们所要求的答案 一旦一个问题可以通过贪心法来解决, 那么贪心法一般是解决这个问题的最好办法 由于贪心法的高效性以及其所求得的答案比较接近最优结果, 贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题 150

160 14.2 经典问题 经典问题 贪心算法经典问题像 Huffman 树, 最小生成树,Dijkstra 算法, 0-1 部分背包问题等等.

161 14.3 相关问题 相关问题 贪心算法还有很多应用场景, 下面介绍几种常见的题目 Jump Game Given an array of non-negative integers, you are initially positioned at the first index of the array.each element in the array represents your maximum jump length at that position. Determine if you are able to reach the last index.(leetcode 55) 举例 : A = [2,3,1,1,4], return true. A = [3,2,1,0,4], return false. 贪心 : 时间复杂度 O(n), 空间复杂度 O(1) 以 A = [2,3,1,1,4] 举例, 从后往前看, 其实能到 A[4] 的有 A[1],A[2],A[3], 我们知道从 A[1] 能到 A[4] 那么必然能从 A[1] 到 A[2] 或者 A[3] 再到 A[4], 所以我们直接贪心的选取从 A[3] 到的 A[4]. 以这种策略, 我们每次只需要选择离目的位置最近的那个跳板就可以了. 1 bool canjump(int A[], int n) { 2 int pos = n - 1; 3 while(pos > 0){ 4 int j = pos; 5 while(--j >= 0) 6 // 最近的那个跳板 7 if(a[j] + j >= pos) break; 8 pos = j; 9 } 10 return pos == 0; 11 } Jump Game II Given an array of non-negative integers, you are initially positioned at the first index of the array.each element in the array represents your maximum jump length at that position.your goal is to reach the last index in the minimum number of jumps.(leetcode 45) 举例 : Given array A = [2,3,1,1,4] The minimum number of jumps to reach the last index is 2. (Jump 1 step from index 0 to 1, then 3 steps to the last index.) 贪心 : 时间复杂度 O(n), 空间复杂度 O(1) 我们还是以 A=[2,3,1,1,4] 为例, 我们考虑 A[1], A[2], A[3] 都能到 A[4], 如果存在 A[x] A[3] A[4], 那么必然存在 A[x] A[1] A[4], 因为 A[1] 在 A[3] 前面,A[x] 必须是先越过 A[1]. 所以我们每次选择能到 A[4] 的所有跳板中最远的那个 (A[1]), 这样总跳数必然是最少的. 1 int jump(int A[], int n) { 2 if(n <= 0) return -1; 3 vector<int> left(n, -1); 4 int pos = 0; 5 //left[i 表示目标位置 ] 的最远跳板位置 i 6 for(int i = 0; i < n && pos < n - 1; i++){

162 14.3 相关问题 for(int j = pos + 1; j <= i + A[i] && j < n; j++) 8 left[j] = i; 9 pos = pos > i + A[i]? pos : i + A[i]; 10 } 11 int count = 0; 12 pos = n - 1; 13 while(pos > 0){ 14 count ++; 15 pos = left[pos]; 16 } 17 return pos == 0? count : -1; 18 } 这个选取最远跳板还有一个 trick, 上面代码就是先使用 O(n) 时间产生每个目标位置的最远跳板位置, 很具有技巧性 Gas Station There are N gas stations along a circular route, where the amount of gas at station i is gas[i]. You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations. Return the starting gas station s index if you can travel around the circuit once, otherwise return -1. (leetcode 134) Note: The solution is guaranteed to be unique. 贪心 : 时间复杂度 O(n), 空间复杂度 O(1) 首先, 我们计算 sum(gas) - sum(cost) > 0 以确定有解. 我们可以从左到有扫描, 初始化 sum = 0, 每遇到 A[i], 则 sum = sum + A[i], 每当 sum<0 时候就把此时的 sum 值存起来并令 sum=0, 那么最后扫描到尾部是 sum 必然是大于 0( 因为前面的 sum 都是负数, 最后一个再是负数就总和也是负数了 ), 且那个 sum 的开始值就是我们要找的起点. 以 A = 1, -2, -3, 6, -3, 2 为例, 一路算出的 sum 值为 -1,-3,5. 那么起点就是最后一个 sum 的起点即 6. 1 int cancompletecircuit(vector<int> &gas, vector<int> &cost) { 2 int size = gas.size(); 3 if(size == 0 gas.size()!= cost.size()) 4 return -1; 5 if(accumulate(gas.begin(), gas.end(), 0) < accumulate(cost.begin(), cost.end(), 0)) 6 return -1; 7 int sum = 0; 8 int start = -1; 9 for(int i = 0; i < size; i++){ 10 sum += gas[i] - cost[i]; 11 if(sum >= 0 && start == -1) 12 start = i; 13 if(sum < 0){ 14 sum = 0; 15 start = -1; 16 } 17 } 18 return start; 19 } 这道题挺费脑筋的, 想到这样还要证明为何这是对的还是挺麻烦的

163 14.3 相关问题 Candy There are N children standing in a line. Each child is assigned a rating value. You are giving candies to these children subjected to the following requirements: Each child must have at least one candy. Children with a higher rating get more candies than their neighbors. What is the minimum candies you must give? (leetcode 135) 贪心 : 时间复杂度 O(n), 空间复杂度 O(n) 我们对每个位置设两个变量 left[i] 和 right[i] 分别表示位于 i 左边 / 右边连续的小于 A[i] 的数目. 然后我们可以使用递推公式 left[i] = A[i] > A[i-1]? left[i-1]+1 : 0 来求 left 数组, 对应 right[i] = A[i] > A[i+1]? right[i+1]+1 : 0 来求 right 数组. 这题老实说感觉应该算动态规划, 不过贪心也算是一种特殊的动态规划了, 而且 left 和 right 数组的求值都是直接通过最优子问题求出, 不是多个选出的, 从这种角度来说应该算是贪心算法. 1 int candy(vector<int> &ratings) { 2 int size = ratings.size(); 3 vector<int> left(size, 0), right(size, 0); 4 for(int i = 1; i < size; i++) 5 if(ratings[i] > ratings[i-1]) left[i] = left[i-1] + 1; 6 else left[i] = 0; 7 for(int i = size - 2; i >= 0; i--) 8 if(ratings[i] > ratings[i+1]) right[i] = right[i+1] + 1; 9 else right[i] = 0; 10 int sum = 0; 11 for(int i = 0; i < size; i++) 12 sum += max(left[i], right[i]) + 1; 13 return sum; 14 } left[i] 和 right[i] 是一种常用的方法 Best Time to Buy and Sell Stock I Say you have an array for which the ith element is the price of a given stock on day i. If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit. (leetcode 121) 贪心 : 时间复杂度 O(n), 空间复杂度 O(1) 这道题只需要找到一个最大值和一个最小值, 且最小值在最大值左边就可以了. 我们可以从右往前遍历并使用 rightmax 记录当前位置及其右边的最大值, 然后那这个最大值减去当前值就算出当前买可以获得的极大值, 然后遍历一遍后这些极大值中的最大值就是全局最大值. 1 int maxprofit(vector<int> &prices) { 2 if(prices.size() == 0) return 0; 3 int rightmax = prices[prices.size() - 1]; 4 int get = 0; 5 for(int i = prices.size() - 2; i >= 0; i--){ 6 rightmax = max(prices[i], rightmax); 7 if(get < rightmax - prices[i]) 8 get = rightmax - prices[i]; 9 } 10 return get; 11 }

164 14.3 相关问题 Best Time to Buy and Sell Stock II Say you have an array for which the ith element is the price of a given stock on day i. Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again). (leetcode 122) 贪心 : 时间复杂度 O(n), 空间复杂度 O(1) 这道题的选择策略就是一旦开涨就买, 一旦下跌就卖, 所以只需要记住每次开涨的值, 然后一遇到下跌就出手, 然后等待下一次开涨. 1 int maxprofit(vector<int> &prices) { 2 int last = -1; 3 int sum = 0; 4 int size = prices.size(); 5 for(int i = 0; i < size - 1; i++){ 6 if(prices[i] < prices[i+1] && last == -1) 7 last = prices[i]; 8 if(prices[i] > prices[i+1] && last!= -1){ 9 sum += prices[i] - last; 10 last = -1; 11 } 12 } 13 if(last!= -1){ 14 sum += prices[size - 1] - last; 15 } 16 return sum; 17 } 使用 last 值为 -1 标志还没开始入手, 还用来过滤一开始就下跌这种情况 Container With Most Water Given n non-negative integers a1, a2,..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.(leetcode 11) Note:You may not slant the container. 贪心 : 时间复杂度 O(n), 空间复杂度 O(1) 这题采用夹逼法, 首先选取左边板为 A[0], 右边板为 A[n-1], 这两个边假设可以盛放水量为 water, 那么我们选取左边板和右边板中的较小的那个, 因为这个较少的值决定盛水槽的高度, 所以在宽度已经最大的情况下, 要想增加盛水量只能试着升高高度, 那么只能使得这个两个边板较小的那个向中间移动, 以寻找最大盛水量. 假设我们的最优解为中间的某两个边板子, 设为 A[i] 和 A[j], 且设 A[i] < A[j]( 反之一样 ), 我们目前的两个边板设为 A[l], A[r], 那么在两个边板都没到达最大值的边板位置之前必然有 mina[l], A[r] < mina[i], A[j], 接着有一个边到达最大值的位置, 假设是 A[l] == A[i], 那么必然有 A[r] < mina[l], A[j], 所以会一直移动到 A[r] == A[j], 即是最大值. 1 int maxarea(vector<int> &height) { 2 int water = 0; 3 int left = 0, right = height.size() - 1; 4 while(left < right){

165 14.3 相关问题 water = max(water, min(height[left], height[right])*(right - left)); 6 if(height[left] < height[right]) left++; 7 else right--; 8 } 9 return water; 10 } 这道题虽然叫夹逼法, 其实也是贪心的一种体现, 因为每次内收较矮的一边.

166 第 15 章动态规划 15.1 基本概念 动态规划 ( 英语 :Dynamic programming,dp)[1] 是一种在数学 计算机科学和经济学中使用的, 通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法 动态规划常常适用于有重叠子问题 [2] 和最优子结构性质的问题, 动态规划方法所耗时间往往远少于朴素解法 动态规划背后的基本思想非常简单 大致上, 若要解一个给定问题, 我们需要解其不同部分 ( 即子问题 ), 再合并子问题的解以得出原问题的解 通常许多子问题非常相似, 为此动态规划法试图仅仅解决每个子问题一次, 从而减少计算量 : 一旦某个给定子问题的解已经算出, 则将其记忆化存储, 以便下次需要同一个子问题解之时直接查表 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用 动态规划的实现大体分为自顶向下的记忆化递归搜索和自底向上的迭代实现. 157

167 15.2 经典问题 经典问题 动态规划的经典问题像 0-1 背包问题, 完全背包问题, 多重背包问题,DAG 上的 DP 问题, 最长递增子序列,LCS, 矩阵链乘, 最大子矩形问题等等等. 可以参考戴方勤的著作 Maximum Subarray Find the contiguous subarray within an array (containing at least one number) which has the largest sum.(leetcode 53) 举例 : For example, given the array [2,1,3,4,1,2,1,5,4], the contiguous subarray [4,1,2,1] has the largest sum = 6. DP : 时间复杂度 O(n), 空间复杂度 O(n) 设动态规划变量为 dp[i] 表示以 A[i] 为结尾的最长连续和, 那么 target=max{dp[i]} dp 的递推关系式 : dp[i] = { 0 i = 0 max{a[i], A[i] + dp[i 1]} other 1 int maxsubarray(int A[], int n) { 2 if(n <= 0) return 0; 3 int maxcon = A[0]; 4 vector<int> con(n, A[0]); 5 for(int i = 1; i < n; i++){ 6 con[i] = max(a[i], con[i-1] + A[i]); 7 if(maxcon < con[i]) maxcon = con[i]; 8 } 9 return maxcon; 10 } 这题是非常经典的动态规划题目, 这类问题通常是以某某为结尾或者某某为开始作为规划目标 Maximal Rectangle Given a 2D binary matrix filled with 0 s and 1 s, find the largest rectangle containing all ones and return its area.(leetcode 85) DP : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 这便是经典的最大子矩形问题的一个变种, 最大子矩形问题中, 允许边界有障碍点, 但是本题是不允许边界有障碍点 (0), 不过本题的解法和最大子矩形问题的解法一样, 都是对矩形中每点求其悬线, 悬线左边距离和右边距离, 然后在每个悬线左右扫描形成的极大子矩形中选取一个为最大子矩形. 我们首先来声明几个定义 : 悬线 height[i][j]: 矩阵中点 A[i][j] 的悬线指从该点出发向上走, 走到第一个障碍点或者边界的距离 ( 含本点 ).

168 15.2 经典问题 159 左边距离 left[i][j]: 矩阵中点 A[i][j] 的左边距离指的是该点的悬线向左移动在不会碰到障碍点或者边界的情况下能移动的最大距离 ( 含本点 ). 右边距离 right[i][j]: 矩阵中点 A[i][j] 的右边距离指的是该点的悬线向右移动在不会碰到障碍点或者边界的情况下能移动的最大距离 ( 含本点 ). 我们以矩阵 A 为例 : A = 那么点 A[2][1] 位置的悬线高度为 2, 左边距离为 1, 右边距离为 2. 所以点 A[2][1] 悬线左右扫面形成的矩形面积为 2*(1+2-1) = 4 于是我们 target = max{height[i][j]*(left[i][j] + right[i][j] - 1)} 另外对于 height,left,right 的求值都有递推公式可以算出 : 0 A[i][j] = 0 height[i][j] = 1 A[i][j]! = 0 & i = 0 1+height[i 1][j] A[i][j]! = 0 & i! = 0 假设 left zero 为 A[i][j] 同行中左边最近的那个 0 的位置 0 A[i][j] = 0 lef t[i][j] = i left z ero A[i][j]! = 0 & i = 0 min(i left z ero,left[i 1][j]) A[i][j]! = 0 & i! = 0 假设 right zero 为 A[i][j] 同行中右边最近的那个 0 的位置 0 A[i][j] = 0 right[i][j] = right z ero i A[i][j]! = 0 & i = 0 min(right z ero i,right[i 1][j]) A[i][j]! = 0 & i! = 0 1 int maximalrectangle(vector<vector<char> > &matrix){ 2 int n = matrix.size(); 3 if(n == 0) return 0; 4 int m = matrix[0].size(); 5 vector<vector<int> > height(n, vector<int>(m, 0)); 6 // 悬线包含本元素, 7 vector<vector<int> > left(n, vector<int>(m, 0)); 8 // 左边包含本元素, 9 vector<vector<int> > right(n, vector<int>(m, 0)); 10 // 右边包含本元素, 11 // 计算悬线 12 for(int i = 0; i < n; i++) 13 for(int j = 0; j < m; j++){ 14 if(matrix[i][j]!= 0 ){ 15 height[i][j] = i == 0? 1 : height[i-1][j] + 1; 16 } 17 } 18 // 计算左边界 19 for(int i = 0; i < n; i++){ 20 int left_zero = -1; 21 for(int j = 0; j < m; j++){ 22 if(matrix[i][j]!= 0 ){

169 15.2 经典问题 if(j == 0) left[i][j] = 1; 24 else{ 25 if(height[i][j] == 1){ 26 left[i][j] = j - left_zero; 27 }else{ 28 left[i][j] = min(j - left_zero, left[i-1][j]); 29 } 30 } 31 }else{ 32 left_zero = j; 33 } 34 } 35 } 36 // 计算右边界 37 for(int i = 0; i < n; i++){ 38 int right_zero = m; 39 for(int j = m-1; j >= 0; j--){ 40 if(matrix[i][j]!= 0 ){ 41 if(j == m-1) right[i][j] = 1; 42 else{ 43 if(height[i][j] == 1){ 44 right[i][j] = right_zero - j; 45 }else{ 46 right[i][j] = min(right_zero - j, right[i-1][j]) ; 47 } 48 } 49 }else{ 50 right_zero = j; 51 } 52 } 53 } 54 // 计算每个悬线左右扫描确定的极大子矩阵 55 int maxrect = 0; 56 for(int i = 0; i < n; i++){ 57 for(int j = 0; j < m; j++){ 58 if(matrix[i][j]!= 0 ){ 59 int cur = height[i][j]*(left[i][j] + right[i][j] - 1); 60 maxrect = max(maxrect, cur); 61 } 62 } 63 } 64 return maxrect; 65 } 这题是非常经典的动态规划题目, 这类问题的更详细解释可以参考王知昆的论文 Triangle Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.(leetcode 120) 举例 : given the following triangle 2, 3,4,

170 15.2 经典问题 161 6,5,7, 4,1,8,3 The minimum path sum from top to bottom is 11 (i.e., = 11). Note:Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle. DP : 时间复杂度 O(n 2 ), 空间复杂度 O(n) 设动态规划变量为 dp[i][j] 表示处于 triangle[i][j] 到顶点的最短距离, 那么 target=min{dp[n-1][j]} dp 的递推关系式 : triangle[i][j] i = 0 dp[i 1][j] j = 0 dp[i][j] = dp[i 1][j 1] j = triangle[i].size() 1 min(dp[i 1][j 1],dp[i 1][j])+triangle[i][j] other 1 int minimumtotal(vector<vector<int> > &triangle) { 2 int n = triangle.size(); 3 if( n == 0) return 0; 4 vector<int> odp(triangle[0]); 5 vector<int> ndp(triangle[0]); 6 for(int i = 1; i < n; i++){ 7 odp = ndp; 8 ndp.clear(); 9 ndp.resize(i+1); 10 for(int j = 0; j < triangle[i].size(); j++){ 11 ndp[j] = triangle[i][j] + min((j == 0? INT_MAX : odp[j-1]), 12 (j == triangle[i].size()-1? INT_MAX : odp[j ])); 13 } 14 } 15 auto iter = min_element(ndp.begin(), ndp.end()); 16 return *iter; 17 } 我们可以看到 dp[i] 行变量只依赖 dp[i-1] 变量, 所以可以使用滚动数组将 O(n 2 ) 空间复杂度降为 O(n) House Robber You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night. Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police. (leetcode 198) DP : 时间复杂度 O(n), 空间复杂度 O(1) 这题就是经典的最大无连续子序列问题, 我们假设 dp[i] 表示 nums[0..i] 可以 rob 的最大价值,target

171 15.2 经典问题 162 = dp[n-1] 所以我们有递推公式 : nums[0] i = 0 dp[i] = max(nums[0],nums[1]) i = 1 max(nums[i]+dp[i 2],dp[i 1]) other 我们可以在编写代码时候对 dp 进行状态压缩以减少空间复杂度 1 int rob(vector<int>& nums) { 2 int n = nums.size(); 3 if(n == 0) return 0; 4 if(n == 1) return nums[0]; 5 int dp1 = nums[0], dp2 = max(nums[0], nums[1]); 6 for(int i = 2; i < n; i++){ 7 int cur = max(nums[i] + dp1, dp2); 8 dp1 = dp2; 9 dp2 = cur; 10 } 11 return dp2; 12 }

172 15.3 相关问题 相关问题 动态规划算法还有很多应用场景, 下面介绍几种常见的题目 Longest Valid Parentheses Given a string containing just the characters ( and ), find the length of the longest valid (well-formed) parentheses substring. For ((), the longest valid parentheses substring is (), which has length = 2. Another example is )()()), where the longest valid parentheses substring is ()(), which has length = 4. (leetcode 32) DP : 时间复杂度 O(n), 空间复杂度 O(n) 设动态规划变量为 dp[i] 表示以 s[i] 为开始的最长匹配串长度. 以 )()()) 为例,dp[i] 分别为 0,4,0,2,0,0. 所以 target = max{dp[i]} 即为 4. dp 的递推关系式 : 0 s[i] = ) i = n 1 2+dp[i+2] s[i,i+1] = () dp[i] = 0 s[i,i+1] = (( & i+dp[i+1] = n 1 0 s[i,i+1] = (( & s[i+dp[i+1]] = ( 2+dp[i+1]+dp[i+dp[i+1]+2] s[i,i+1] = (( & s[i+dp[i+1]] = ) 1 i n t longestvalidparentheses( string s) { 2 int size = s.size(); 3 vector<int> dp(size + 1, 0); 4 int max = 0; 5 for(int i = size - 2; i >= 0; i--){ 6 if(s[i] == ( ){ 7 if(s[i+1] == ) ){ 8 dp[i] = 2 + dp[i+2]; 9 }else{ 10 if(i + dp[i+1] < size - 1){ 11 if(s[i + dp[i+1] + 1] == ) ) 12 dp[i] = 2 + dp[i+1] + dp[i + dp[i+1] + 2]; 13 } 14 } 15 } 16 if(max < dp[i]) 17 max = dp[i]; 18 } 19 return max; 20 } Unique Paths A robot is located at the top-left corner of a m x n grid (marked Start in the diagram below). The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked Finish in the diagram below). How many possible unique paths are there?(leetcode 62)

173 15.3 相关问题 164 DP : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 设动态规划变量为 dp[i][j] 表示从起点到 i,j 位置的路径数目, 那么 target = dp[n-1][m-1] dp 的递推关系式 : dp[i][j] = { 1 i = 0 j = 0 dp[i 1][j]+dp[i][j 1] other 1 int uniquepaths(int m, int n) { 2 if(m < 1 n < 1) return 0; 3 vector<vector<int> > dp(m, vector<int>(n, 1)); 4 for(int i = 1; i < m; i++) 5 for(int j = 1; j < n; j++) 6 dp[i][j] = dp[i-1][j] + dp[i][j-1]; 7 return dp[m-1][n-1]; 8 } Unique Paths II Follow up for Unique Paths : Now consider if some obstacles are added to the grids. How many unique paths would there be? An obstacle and empty space is marked as 1 and 0 respectively in the grid.(leetcode 63) DP : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 设动态规划变量为 dp[i][j] 表示从起点到 i,j 位置的路径数目, 那么 target = dp[n-1][m-1] dp 的递推关系式 : 0 A[i][j] = 1 1 A[0][0] = 0 & i = 0 & j = 0 dp[i][j] = dp[i][j 1] A[i][j] = 0 & i = 0 & j! = 0 dp[i 1][j] A[i][j] = 0 & i! = 0 & j = 0 dp[i 1][j]+dp[i][j 1] A[i][j] = 0 & i! = 0 & j! = 0 1 int uniquepathswithobstacles(vector<vector<int> > &obstaclegrid) { 2 int m = obstaclegrid.size(); 3 int n = obstaclegrid[0].size(); 4 vector<vector<int> > dp(m, vector<int>(n, 1)); 5 for(int i = 0; i < m; i++) 6 for(int j = 0; j < n; j++){ 7 if(obstaclegrid[i][j]) dp[i][j] = 0; 8 else{ 9 if(i + j!= 0) 10 dp[i][j] = (i > 0? dp[i-1][j] : 0) + (j > 0? dp[i][j -1] : 0); 11 } 12 } 13 return dp[m-1][n-1]; 14 } 这题和 Unique Paths 唯一的区别就是注意路障不能选

174 15.3 相关问题 Minimum Path Sum Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.(leetcode 64) Note: You can only move either down or right at any point in time. DP : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 设动态规划变量为 dp[i][j] 表示从起点到 i,j 位置的最短路径长度, 那么 target = dp[n-1][m-1] dp 的递推关系式 : A[0][0] i = 0 & j = 0 A[i][j]+dp[i][j 1] i = 0 & j! = 0 dp[i][j] = A[i][j]+dp[i 1][j] i! = 0 & j = 0 min(dp[i 1][j],dp[i][j 1])+A[i][j] other 1 int minpathsum(vector<vector<int> > &grid) { 2 int n = grid.size(); 3 int m = grid[0].size(); 4 vector<vector<int> > dp(grid); 5 for(int i = 0; i < n; i++) 6 for(int j = 0; j < m; j++){ 7 if(i + j!= 0){ 8 dp[i][j] = grid[i][j] + 9 min((i > 0? dp[i-1][j] : INT_MAX), (j > 0? dp[i ][j-1] : INT_MAX)); 10 } 11 } 12 return dp[n-1][m-1]; 13 } Climbing Stairs You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?(leetcode 70) DP : 时间复杂度 O(n), 空间复杂度 O(1) 其实本题就是著名的斐波那契数列问题, 设动态规划变量为 dp[i] 为到阶梯 i 的所有可能的路数, 那么 target=dp[n-1]. dp 的递推关系式 : 1 i = 0 dp[i] = 2 i = 1 dp[i 2]+dp[i 1] other 1 int climbstairs(int n) { 2 if(n <= 1) return n; 3 int fn1 = 2, fn2 = 1, fn = 2; 4 for(int i = 3; i <= n; i++){ 5 fn = fn1 + fn2;

175 15.3 相关问题 fn2 = fn1; 7 fn1 = fn; 8 } 9 return fn; 10 } 虽然我们递推公式中是使用 dp[i] 一个数组来解释, 但是因为 dp[i] 只依赖 dp[i-1] 和 dp[i-2], 那么可以压缩状态存储空间到 O(1), 这是节约空间的一种常用手段, 像轮转数组等等 Scramble String Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrings recursively. Below is one possible representation of s1 = great : great / \ gr eat / \ / \ g r e at / \ a t To scramble the string, we may choose any non-leaf node and swap its two children. For example, if we choose the node gr and swap its two children, it produces a scrambled string rgeat. rgeat / \ rg eat / \ / \ r g e at / \ a t We say that rgeat is a scrambled string of great. Similarly, if we continue to swap the children of nodes eat and at, it produces a scrambled string rgtae. rgtae / \ rg tae / \ / \ r g ta e / \ t a We say that rgtae is a scrambled string of great. Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1.(leetcode 87) DP : 时间复杂度 O(n 4 ), 空间复杂度 O(n 3 ) 我们假设 dp[i][j][l] 表示 s1 i,...s1 i+l 1 和 s2 j,...s2 j+l 1 是否互为可转置的.

176 15.3 相关问题 167 dp 的递推关系式 : { s1[i] == s2[j] l = 1 dp[i][j][l] = l 1 k=1 (dp[i][j][k] & dp[i+k][j +k][l k]) (dp[i][j +l k][k] & dp[i+k][j][l k]) other 其中上面的求积符号代表或运算 1 bool isscramble(string s1, string s2) { 2 int n = s1.size(); 3 if(n == 0 s1.size()!= s2.size() ) return false; 4 vector<vector<vector<bool> > > dp(n, vector<vector<bool> >(n, vector <bool>(n+1, false))); 5 for(int i = 0; i < n; i++) 6 for(int j = 0; j < n; j++) 7 dp[i][j][1] = s1[i] == s2[j]; 8 for(int l = 2; l < n+1; l++){ 9 for(int i = 0; i <= n - l; i++) 10 for(int j = 0; j <= n - l; j++){ 11 for(int k = 1; k < l; k++){ 12 dp[i][j][l] = dp[i][j][l] 13 (dp[i][j][k] && dp[i+k][j+k][l-k]) 14 (dp[i][j+l-k][k] && dp[i+k][j][l-k]); 15 } 16 } 17 } 18 return dp[0][0][n]; 19 } 这道题困难之处在于敢不敢这么简单粗暴的递推 Decode Ways A message containing letters from A-Z is being encoded to numbers using the following mapping: A 1 B 2... Z 26 Given an encoded message containing digits, determine the total number of ways to decode it.(leetcode 91) 举例 : Given encoded message 12, it could be decoded as AB (1 2) or L (12). The number of ways decoding 12 is 2. DP : 时间复杂度 O(n), 空间复杂度 O(n) 本题有两个需要注意的地方一个是检测非法输入, 比如 00 这样的. 另外就是算出多少种解密方法. 检测非法输入 : 非法输入的检测使用 NFA 自动机来实现, 这样是最优美和最简单的, 状态转移图如下, 其中起始状态为 0, 非法状态为 3:

177 15.3 相关问题 计算解密数 : 设 dp[i] 表示 s[0..i-1] 可解密的数目, 那么我们有递推公式如下 : 1 i = 0 dp[i 2] i! = 0 & s[i 1] = 0 dp[i] = dp[i 1]+dp[i 2] i > 1 & s[i 2] = 1 (s[i 2] = 2 & s[i 1] <= 6 ) dp[i 1] other 1 bool isvalid(string &s){ 2 int size = s.size(); 3 int dfa[4][10] = { 4 {3,1,1,2,2,2,2,2,2,2}, 5 {0,1,1,2,2,2,2,2,2,2}, 6 {3,1,1,2,2,2,2,2,2,2}, 7 {3,3,3,3,3,3,3,3,3,3} 8 }; 9 int status = 0; 10 for(int i = 0; i < size; i++) 11 status = dfa[status][s[i]- 0 ]; 12 return status!= 3; 13 } int numdecodings(string s) { 16 int size = s.size(); 17 if(size == 0 s[0] == 0!isValid(s)) return 0; 18 vector<int> dp(size+1, 1); 19 for(int i = 1; i < size; i++){ 20 if(s[i] == 0 ) 21 dp[i+1] = dp[i-1]; 22 else 23 if(s[i-1] == 1 (s[i-1] == 2 && s[i] <= 6 )) 24 dp[i+1] = dp[i] + dp[i-1]; 25 else 26 dp[i+1] = dp[i]; 27 } 28 return dp[size]; 29 } 这题递推公式很简单, 但是考虑到非法输入比较麻烦, 最简单的做法就是先检测一边, 使用有限自动机是最优雅的检测手段 Interleaving String Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2.(leetcode 97) 举例 : Given: s1 = aabcc, s2 = dbbca,

178 15.3 相关问题 169 When s3 = aadbbcbcac, return true. When s3 = aadbbbaccc, return false. DP : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 我们假设 dp[i][j] 表示 s1[0..i-1] 与 s2[0...j-1] 是否能交叉构造出 s3[0..i+j-1] dp 的递推关系式 : s2[0..j 1] == s3[0..j 1] i = 0 dp[i][j] = s1[0..i 1] == s3[0..i 1] j = 0 (s1[i 1] == s3[i+j 1] & dp[i 1][j]) (s2[j 1] == s3[i+j 1] & dp[i][j 1]) other 1 bool isinterleave(string s1, string s2, string s3) { 2 int n1 = s1.size(), n2 = s2.size(), n3 = s3.size(); 3 if(n1 + n2!= n3) return false; 4 vector<vector<int> > dp(n1+1, vector<int>(n2+1, false)); 5 dp[0][0] = true; 6 for(int i = 0; i < n1; i++) 7 if(s1[i] == s3[i]) dp[i+1][0] = true; 8 else break; 9 for(int i = 0; i < n2; i++) 10 if(s2[i] == s3[i]) dp[0][i+1] = true; 11 else break; 12 for(int i = 0; i < n1; i++) 13 for(int j = 0; j < n2; j++){ 14 dp[i+1][j+1] = (s1[i] == s3[i+j+1] && dp[i][j+1]) 15 (s2[j] == s3[i+j+1] && dp[i+1][j]); 16 } 17 return dp[n1][n2]; 18 } Distinct Subsequences Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, ACE is a subsequence of ABCDE while AEC is not).(leetcode 115) 举例 : S = rabbbit, T = rabbit Return 3. DP : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 我们假设 dp[i][j] 表示 S[0..i-1] 能抽出 T[0..j-1] 的种类数, 那么 target = dp[n][m] dp 的递推关系式 : 0 i < j 1 j = 0 dp[i][j] = dp[i 1][j 1]+dp[i 1][j] S[i 1] == T[j 1] dp[i 1][j] other

179 15.3 相关问题 int numdistinct(string S, string T) { 2 int n = S.size(); 3 int m = T.size(); 4 vector<vector<int> > dp(n+1, vector<int>(m+1, 0)); 5 for(int j = 0; j < m; j++) 6 for(int i = j; i < n; i++){ 7 if(s[i] == T[j]){ 8 dp[i+1][j+1] = dp[i][j+1] + (j == 0? 1 : dp[i][j]); 9 }else{ 10 dp[i+1][j+1] = dp[i][j+1]; 11 } 12 } 13 return dp[n][m]; 14 } 这题和 LCS 问题很像 Best Time to Buy and Sell Stock III Say you have an array for which the ith element is the price of a given stock on day i. Design an algorithm to find the maximum profit. You may complete at most two transactions.(leetcode 123) Note: You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again). DP : 时间复杂度 O(n), 空间复杂度 O(n) 我们可以试着这样考虑 由于能买两次, 所以可以考虑将 prices[0...n-1] 插入 n+1 个分界点, 每选取一个都可以将 prices 数组分为两份, 我们可以在这两份子数组中分别求出一次购买的最大利润, 然后两者相加就是此分界点确定的两次购买最大利润. 我们的最优解也必然是先是一次购买又是一次购买 ( 如果只需要一次购买, 可以想成是有一个第二次购买且是当天购买当天卖出收益 0 这种情况 ), 最优解的购买方式也必然是被某个 ( 或者多个 ) 分界点确定的, 所以我们的目标就是求出每个分界点确定的最大利润, 然后在这里面取最大的那个就行了. 我们假设 dp[i] 为分界点 i, 将 prices 数组分为 prices[0..i-1] 和 prices[i..n-1], 这两个子数组的单次购买最大利润值分别标记为 left[i] 和 right[i] 那么有递推关系式 : 假设 left min 为 prices[0...i-1] 中的最小值 { 0 i = 0 left[i] = max(left[i 1],prices[i 1] left min) other 假设 right max 为 prices[i..n-1] 中的最大值 { 0 i = n right[i] = max(right[i + 1], right max prices[i 1]) other 1 int maxprofit(vector<int> &prices) { 2 int n = prices.size(); 3 vector<int> left(n+1, 0); dp[i] = left[i]+right[i]

180 15.3 相关问题 //left[i 表示 ]a[0],...a[i 单次购买最大利润 -1] 5 vector<int> right(n+1, 0); 6 //right[i 表示 ]a[i],...a[n 单次购买最大利润 -1] 7 int left_min = INT_MAX, right_max = INT_MIN; 8 for(int i = 1; i <= n; i++){ 9 left_min = min(prices[i-1], left_min); 10 left[i] = max(left[i-1], prices[i-1] - left_min); 11 } 12 for(int i = n-1; i >= 0; i--){ 13 right_max = max(prices[i], right_max); 14 right[i] = max(right_max - prices[i], right[i+1]); 15 } 16 // 以为切割点, 求左右两部分各自最大利润再综合 i 17 int maxearn = INT_MIN; 18 for(int i = 0; i <= n; i++){ 19 maxearn = max(maxearn, left[i] + right[i]); 20 } 21 return maxearn; 22 } 这道题先采用分治的做法把区间两次股票问题转为区间单次股票问题, 区间单次股票问题很简单, 参见 Best Time Buy and Sell Stock I Best Time to Buy and Sell Stock IV Say you have an array for which the ith element is the price of a given stock on day i. Design an algorithm to find the maximum profit. You may complete at most k transactions. (leetcode 188) Note: You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again). DP : 时间复杂度 O(kn), 空间复杂度 O(n) 首先需要考虑一种特殊情况 : k > price.size()/2, 这种情况就是等同于你可以购买无数次, 也就是你需要把每个增长区间都计算进去就可以了. 这样就可以在 O(n) 时间解决这种情况而不是 O(kn). 对于其他情况, 我们假设 left[i] 表示 prices[i] 的左边连续最小值的位置,dp[i][p] 表示至多购买 p 次并且以 prices[i] 为最后一次卖出点的最大利润,r[i][p] 表示至多购买 p 次并且最后一次卖出点在 prices[i] 之前的最大利润, 所以 target = r[n-1][k] 我们可以有递推公式 0 i = 0 left[i] = left[i 1] prices[i] >= prices[i 1] i prices[i] < prices[i 1] { prices[i] prices[0] lef t[i] = 0 dp[i][p] = max(prices[i] prices[j]+r[j 1][p 1],prices[i] prices[j 1]+dp[j 1][p]) other 其中 j = left[i] r[i][p] = { 0 p = 0 r = 0 max(r[i 1][p],dp[i][p]) other

181 15.3 相关问题 172 从递推公式可以看出,dp 和 r 可以状态压缩以节省空间 1 int maxprofit(int k, vector<int>& prices) { 2 int n = prices.size(); 3 // simple case, very important 4 if (k > n/2){ 5 int ans = 0; 6 for (int i=1; i<n; ++i){ 7 ans += max(prices[i] - prices[i-1],0); 8 } 9 return ans; 10 } 11 k = min(k, n/2); 12 if(n <= 1) return 0; 13 vector<int> dp(n, 0), r(n, 0), left(n,0); 14 for(int i = 1; i < n; i++){ 15 if(prices[i] >= prices[i-1]){ 16 left[i] = left[i-1]; 17 } 18 else{ 19 left[i] = i; 20 } 21 } 22 for(int p = 1; p <= k; p++){ 23 for(int i = 1; i < n; i++){ 24 int j = left[i]; 25 if(j == 0) dp[i] = prices[i] - prices[0]; 26 else 27 dp[i] = max(prices[i] - prices[j-1] + dp[j-1], 28 prices[i] - prices[j] + r[j-1]); 29 } 30 for(int i = 1; i < n; i++) 31 r[i] = max(r[i-1], dp[i]); 32 } 33 return r[n-1]; 34 } Palindrome Partitioning II Given a string s, partition s such that every substring of the partition is a palindrome. Return the minimum cuts needed for a palindrome partitioning of s.(leetcode 132) 举例 : given s = aab, Return 1 since the palindrome partitioning [ aa, b ] could be produced using 1 cut. DP : 时间复杂度 O(n 2 ), 空间复杂度 O(n 2 ) 我们假设 dp[i] 表示 s[0..i-1] 最小切分下的段数, 那么 tgarget = dp[n] - 1. dp 的递推关系式 : dp[i] = { 0 i = 0 mindp[k] + 1v 0 <= k < iˇs[k i]ispalindrome other

182 15.3 相关问题 int mincut(string s) { 2 int n = s.size(); 3 vector<vector<bool> > palin(n, vector<bool>(n, false)); 4 for(int i = 0; i < n; i++){ 5 for(int j = 0; i + j < n && i - j >= 0; j++){ 6 if(s[i-j] == s[i+j]) palin[i-j][i+j] = true; 7 else break; 8 } 9 } 10 for(int i = 0; i < n-1; i++){ 11 for(int j = 0; i + j < n - 1 && i - j >= 0; j++){ 12 if(s[i-j] == s[i+j+1]) palin[i-j][i+j+1] = true; 13 else break; 14 } 15 } 16 vector<int> dp(n+1, 0); 17 for(int i = 0; i < n; i++){ 18 int mincur = INT_MAX; 19 for(int j = 0; j <= i; j++){ 20 if(palin[j][i]){ 21 mincur = min(mincur, dp[j] + 1); 22 } 23 } 24 dp[i+1] = mincur; 25 } 26 return dp[n] - 1; 27 } 这题很有普世性, 都是一维的 dp, 但是对于 dp[i] 需要选取前面所有合适的候选中的最佳候选而不是直接得出最佳候选, 所以时间复杂度就是 O(n 2 ) Word Break Given a string s and a dictionary of words dict, determine if s can be segmented into a spaceseparated sequence of one or more dictionary words.(leetcode 139) 举例 : s = leetcode, dict = [ leet, code ]. Return true because leetcode can be segmented as leet code. DP : 时间复杂度 O(n 2 ), 空间复杂度 O(n) 假设 dp[i] 表示 s[0...i-1] 能否由词典 dict 里面的词构成,target = dp[n] dp 的递推关系式 : dp[i] = 其中上面的求积符号表示逻辑或. { true i = 0 i 1 k=0dp[k] & s[k +1..i] dict other 1 bool wordbreak(string s, unordered_set<string> &dict) { 2 int n = s.size(); 3 vector<bool> dp(n+1, false); 4 dp[0] = true; 5 for(int i = 0; i < n; i++){

183 15.3 相关问题 for(int j = i; j >= 0; j--){ 7 if(dict.count(s.substr(j, i - j + 1))!= 0){ 8 dp[i+1] = dp[j]; 9 if(dp[i+1]) break; 10 } 11 } 12 } 13 return dp[n]; 14 } 这题也是一维 dp 问题, 对于每个 dp[i], 同样也是综合所有候选方案确定, 时间复杂度为 O(n 2 ) Word Break II Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word. Return all such possible sentences.(leetcode 140) 举例 : s = catsanddog, dict = [ cat, cats, and, sand, dog ]. A solution is [ cats and dog, cat sand dog ]. DP : 时间复杂度 O(n 2 ), 空间复杂度 O(n 2 ) 假设 dp[i] 表示 s[0...i-1] 由词典 dict 里面的词构成所有可能,target = dp[n] dp 的递推关系式 : { i = 0 dp[i] = i 1 k=0ele+s[k +1..i], ele dp[k] & s[k +1..i] dict other 1 vector<string> wordbreak(string s, unordered_set<string> &dict) { 2 int n = s.size(); 3 vector<vector<string> > dp(n+1, vector<string>()); 4 dp[0].push_back(""); 5 for(int i = 0; i < n; i++) 6 for(int j = i; j >= 0; j--){ 7 string tail = s.substr(j, i - j + 1); 8 if(dict.count(tail)!= 0){ 9 for(int k = 0; k < dp[j].size(); k++){ 10 dp[i+1].push_back(dp[j][k] + " " + tail); 11 } 12 } 13 } 14 for(int i = 0; i < dp[n].size(); i++) 15 dp[n][i].erase(dp[n][i].begin()); 16 return dp[n]; 17 } 这题这样解在 leetcode 上通不过, 因为空间复杂度太大..., 于是可以使用下面的这种方法 DP+DFS : 时间复杂度 O(n!), 空间复杂度 O(n) 假设 dp[i] 表示 s[0...i-1] 由词典 dict 里面的词构成的每种可能, 但是不像上面记下整个切分的

184 15.3 相关问题 175 词, 而是只记录最后一个切分词的位置 dp 的递推关系式 : dp[i] = { 1 i = 0 i k=0 k, dp[k] / & s[k +1..i] dict other 有了上面的切分之后就可以使用 DFS 来自下而上的构造最终解, 虽然这里面可能会有很多重复计算, 但是空间复杂度很低了... 1 void genpath(string &s, vector<vector<int> > &dp, vector<string> &ret, 2 int pos, vector<string>& path){ 3 if(pos < 0){ 4 int size = path.size(); 5 string cur = path[size-1]; 6 for(int i = size - 2; i >= 0; i--){ 7 cur += " " + path[i]; 8 } 9 ret.push_back(cur); 10 return; 11 } 12 for(int i = 0; i < dp[pos+1].size(); i++){ 13 path.push_back(s.substr(dp[pos+1][i], pos - dp[pos+1][i] + 1)); 14 genpath(s, dp, ret, dp[pos+1][i] - 1, path); 15 path.pop_back(); 16 } 17 } vector<string> wordbreak(string s, unordered_set<string> &dict) { 20 int n = s.size(); 21 vector<vector<int> > dp(n+1, vector<int>()); 22 dp[0].push_back(-1); 23 for(int i = 0; i < n; i++) 24 for(int j = i; j >= 0; j--){ 25 string tail = s.substr(j, i - j + 1); 26 if(dict.count(tail)!= 0 && dp[j].size()!= 0){ 27 dp[i+1].push_back(j); 28 } 29 } 30 vector<string> ret; 31 vector<string> path; 32 genpath(s, dp, ret, n-1, path); 33 return ret; 34 } 这题按理说还是使用第一种方法比较好, 这是空间换取时间的做法, 无奈 leetcode 上限制了内存使用只能使用第二种方法了 Maximum Product Subarray Find the contiguous subarray within an array (containing at least one number) which has the largest product.(leetcode 152) 举例 : given the array [2,3,-2,4], the contiguous subarray [2,3] has the largest product = 6.

185 15.3 相关问题 176 DP : 时间复杂度 O(n), 空间复杂度 O(n) 我们假设 maxcon[i] 表示以 A[i] 为结尾的最大连续子串积,minCon[i] 表示以 A[i] 为结尾的最小连续子串积. 那么 target = max{maxcon[i]}. maxcon 和 mincon 的递推关系式 : A[0] i = 0 maxcon[i] = max(a[i],maxcon[i 1] A[i]) i! = 0 & A[i] >= 0 max(a[i], mincon[i 1] A[i]) other A[0] i = 0 mincon[i] = min(a[i],mincon[i 1] A[i]) i! = 0 & A[i] >= 0 min(a[i], maxcon[i 1] A[i]) other 1 int maxproduct(int A[], int n) { 2 if(n <= 0) return 0; 3 vector<int> maxcon(n, A[0]); 4 vector<int> mincon(n, A[0]); 5 for(int i = 1; i < n; i++){ 6 if(a[i] >= 0){ 7 maxcon[i] = max(maxcon[i-1]*a[i], A[i]); 8 mincon[i] = min(mincon[i-1]*a[i], A[i]); 9 }else{ 10 maxcon[i] = max(mincon[i-1]*a[i], A[i]); 11 mincon[i] = min(maxcon[i-1]*a[i], A[i]); 12 } 13 } 14 return *max_element(maxcon.begin(), maxcon.end()); 15 } 这题和最大连续子串和几乎一样, 唯一区别是需要维护两个动规量, 思路都是列出所有结尾的可能得到的值, 取其中最大的一个 Dungeon Game The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. The dungeon consists of M x N rooms laid out in a 2D grid. Our valiant knight (K) was initially positioned in the top-left room and must fight his way through the dungeon to rescue the princess. The knight has an initial health point represented by a positive integer. If at any point his health point drops to 0 or below, he dies immediately. Some of the rooms are guarded by demons, so the knight loses health (negative integers) upon entering these rooms; other rooms are either empty (0 s) or contain magic orbs that increase the knight s health (positive integers). In order to reach the princess as quickly as possible, the knight decides to move only rightward or downward in each step. Write a function to determine the knight s minimum initial health so that he is able to rescue the princess. (leetcode 174)

186 15.3 相关问题 177 举例 : Given the dungeon below, the initial health of the knight must be at least 7 if he follows the optimal path RIGHT RIGHT DOWN DOWN. Note: The knight s health has no upper bound. Any room can contain threats or power-ups, even the first room the knight enters and the bottom-right room where the princess is imprisoned. DP : 时间复杂度 O(n 2 ), 空间复杂度 O(n 2 ) 这道题很显然会想到用 DP 来做, 但是我一开始是从出发点开始构造 DP 推导公式, 但是发现这样会有问题, 因为你无法仅仅根据开始点到当前点的情况的就能判断出每步该选择向右还是向下, 所以稍微改变一下思路, 可以从结束点倒过来 DP, 这样做的好处是, 对于每个待判断的点, 它已经知道向右或者向下的将来代价, 只需要选这两个较小的那个就好, 因为无论向右还是向下, 之前的路都是一样的, 所以全局最优解就向右和向下这个两个子最优解的综合. 我们假设 dp[i][j] 代表从 (i, j) 到达终点的所需最小血值, 所以我们有 target = dp[0][0] 递推公式如下 : makedp(dungeon i,j ) i = n 1,j = m 1 makedp(dungeon i,j dp[i][j +1]) i = n 1 dp[i][j] = makedp(dungeon i,j dp[i+1][j]) j = m 1 min(makedp(dungeon i,j dp[i][j +1]),makeDp(dungeon i,j dp[i+1][j])) other makedp is a function as the follow: 1 int makedp(int data){ 2 return data < 0? -data : 1; 3 } 4 5 int calculateminimumhp(vector<vector<int> > &dungeon) { 6 int n = dungeon.size(); 7 if(n == 0) return 0; 8 int m = dungeon[0].size(); 9 vector<vector<int> > dp(dungeon); 10 for(int i = n - 1; i >= 0; i--)

187 15.3 相关问题 for(int j = m - 1; j >= 0; j--){ 12 if( i == n - 1 && j == m - 1) 13 dp[i][j] = dungeon[i][j] < 0? -dungeon[i][j] + 1 : 1; 14 else{ 15 if(i == n - 1){ 16 dp[i][j] = makedp(dungeon[i][j] - dp[i][j+1]); 17 }else if(j == m - 1){ 18 dp[i][j] = makedp(dungeon[i][j] - dp[i+1][j]); 19 }else{ 20 int right = makedp(dungeon[i][j] - dp[i][j+1]); 21 int down = makedp(dungeon[i][j] - dp[i+1][j]); 22 dp[i][j] = min(right, down); 23 } 24 } 25 } 26 return dp[0][0]; 27 }

188 第 16 章位操作 16.1 基本概念 位操作包括左移, 右移, 与, 或, 非和亦或等. 其中 C++ 中的右移都是算术右移, 即首位补符号位. 还有一点需要注意的是, 我们的这些位操作的操作对象都是补码, 比如 1 2 其实就是 0xffffffff 0x2. 179

189 16.2 经典问题 经典问题 位操作有很多经典问题, 比如将最低位的 1 设置为 0 我们有变量 x, 现在我们希望设置 x 最低位的 1 为 0, 比如 x = 0xff0ff010, 那么设置之后就是 x = 0xff0ff000. 最简单的做法就是 x = x & (x 1);

190 16.3 相关问题 相关问题 位操作通常适于那些每个位上都有显著特征的问题, 比如每个位上的 0,1 分布是成对出现啊,0 和 1 出现的哪个多就取哪个啊等等 Single Number Given an array of integers, every element appears twice except for one. Find that single one. (leetcode 136) Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? 亦或 : 时间复杂度 O(n), 空间复杂度 O(1) 这道题是很经典的一道题, 主要是需要想出一个或者一组操作使得相同的元素能够彼此清除掉而只剩下那一个唯一的操作. 我们可以想一下, 相同的元素在每个位上都是相同的要么都是 0 要么都是 1, 所以我们想到了亦或操作. 1 int singlenumber(vector<int>& nums) { 2 int result = 0; 3 for(int i = 0; i < nums.size(); i++) 4 result ^= nums[i]; 5 return result; 6 } Single Number II Given an array of integers, every element appears three times except for one. Find that single one. (leetcode 137) Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? : 时间复杂度 O(n), 空间复杂度 O(1) 这题和上题不同, 这题相同的元素是有三个. 我们不得不寻找一个更加普适的方法, 我们知道三个一样的数在每一位上要么都是 1 要么都是 0, 所以我们可以对于每个位, 求出这些 1 和 0 的和, 然后模上 3, 那么三三一样的就会被剔除, 剩余的余数就是那个单独的数. 这个方法不仅适用于 3 个数一样的问题, 还适用于 4,5,... 个数一样的问题, 当然两个数一样的问题, 只不过那题可以用更简洁的亦或做. 1 int singlenumber(vector<int>& nums) { 2 int result = 0; 3 vector<int> b(32, 0); 4 for(int i = 0; i < 32; i++){ 5 for(int j = 0; j < nums.size(); j++){ 6 b[i] += (((nums[j] >> i) & 0x1)!= 0); 7 } 8 b[i] %= 3; 9 result += (b[i] << i); 10 } 11 return result; 12 }

191 16.3 相关问题 Majority Element Given an array of size n, find the majority element. The majority element is the element that appears more than n/2 times. You may assume that the array is non-empty and the majority element always exist in the array. (leetcode 169) 记数法 : 时间复杂度 O(n), 空间复杂度 O(1) 这题我最先了解到的解法就是记数法, 因为主元素出现的次数多于总个数的一半, 所以我们可以放着两个椅子, 然后让元素去争抢 : 对于每个新加入的元素, 如果椅子上有与它相同的元素, 那么该椅子记数 +1; 如果没有, 此时如果有空闲椅子, 则该元素抢占该椅子, 然后置此椅子记数为 1; 如果没有空闲椅子, 那么舍弃该元素, 并且两个椅子的记数都 -1, 如果此时某个椅子记数为 0, 则置此椅子为空闲. 最后, 椅子上记数较大的那个元素就是主元素. 这个方法的证明也很简单, 我们可以试想一下, 由于主元素个数超过一半, 又有两个椅子, 所以主元素必然在最终的一个之一, 而且另一个椅子的记数也绝不会超过它. 1 int majorityelement(vector<int>& nums) { 2 if(nums.size() == 0) return -1; 3 int p1 = nums[0], p2, c1 = 1, c2 = 0; 4 for(int i = 1; i < nums.size(); i++){ 5 if(c1 && nums[i] == p1) c1++; 6 else if(c2 && nums[i] == p2) c2++; 7 else if(!c1){ 8 p1 = nums[i]; 9 c1 ++; 10 } 11 else if(!c2){ 12 p2 = nums[i]; 13 c2 ++; 14 }else{ 15 c1--; 16 c2--; 17 } 18 } 19 return c1 > c2? p1 : p2; 20 } 位操作 : 时间复杂度 O(n), 空间复杂度 O(1) 这题还可以这么考虑 : 我们对于数组中每个元素分析它们的每个位, 由于主元素个数超半, 所以每个位的 0,1 个数必然被主元素控制, 如果主元素该位为 1 那么 1 的个数必然超过一半, 反之亦然. 所以我们可以基于这个原则设计算法. 1 int majorityelement(vector<int>& nums) { 2 int result = 0, n = nums.size(); 3 for(int i = 0; i < 32; i++){ 4 int bit = 0; 5 for(int j = 0; j < n; j++) 6 bit += ((nums[j] & (0x1 << i))? 1 : 0); 7 result += ((bit > n/2? 1 : 0) << i); 8 } 9 return result; 10 }

192 16.3 相关问题 Reverse Bits Reverse bits of a given 32 bits unsigned integer. (leetcode 190) 举例 : Given input (represented in binary as ), return (represented in binary as ). Follow Up: If this function is called many times, how would you optimize it? 位操作 : 时间复杂度 O(1), 空间复杂度 O(1) 这题的 Follow Up 很值得注意, 最笨的做法就是建立索引表, 下次查找就不用再计算了. 1 uint32_t reversebits(uint32_t n) { 2 uint32_t result = 0; 3 for(int i = 0; i < 16; i++){ 4 bool front = n & (0x1 << (31 - i)); 5 bool back = n & (0x1 << i); 6 result = result (back << (31 - i)); 7 result = result (front << i); 8 } 9 return result; 10 } 位操作 : 时间复杂度 O(1), 空间复杂度 O(1) 1 uint32_t reversebits(uint32_t n) { 2 n=((n & 0xffff0000) >> 16) ((n & 0x0000ffff) << 16); 3 n=((n & 0xff00ff00) >> 8) ((n & 0x00ff00ff) << 8); 4 n=((n & 0xf0f0f0f0) >> 4) ((n & 0x0f0f0f0f) << 4); 5 n=((n & 0xcccccccc) >> 2) ((n & 0x ) << 2); 6 n=((n & 0xaaaaaaaa) >> 1) ((n & 0x ) << 1); 7 return n; 8 } Number of 1 Bits Write a function that takes an unsigned integer and returns the number of 1 bits it has (also known as the Hamming weight). (leetcode 191) 举例 : The 32-bit integer 11 has binary representation , so the function should return 3. 位操作 : 时间复杂度 O(1), 空间复杂度 O(1) 这题用经典问题 x & (x 1) 即可 1 int hammingweight(uint32_t n) { 2 int count = 0; 3 while(n){ 4 n = (n & (n - 1)); 5 count ++;

193 16.3 相关问题 } 7 return count; 8 } Bitwise AND of Numbers Range Given a range [m,n] where 0 = m = n = , return the bitwise AND of all numbers in this range, inclusive. (leetcode 201) 举例 : Given the range [5,7], you should return 4. 位操作 : 时间复杂度 O(n), 空间复杂度 O(1) 首先声明高位区间 : 高位都一样, 低位从 0x0 到 0xff..f 区间. 比如 [0xc1a000,0xc1afff] 然后我们有一个规律, 在区间 [a,b] 中如果 gap = b - a + 1 小于某个高位区间的大小, 那么 a 和 b 的对应高位是一样的. 比如区间 [13,16] 中 gap = = 4 8, 所以高 29 位是一样的. 基于上面的规律, 不断的扩大高位区间直到 gap 大于高位区间大小, 这样就确定了哪些高位是一样的. 1 int rangebitwiseand(int m, int n) { 2 if(m == 0 && n == INT_MAX) return 0; 3 int gap = n - m + 1; 4 if(gap <= 1) return n; 5 int pos = 0; 6 while(pos < 32 && ((1 << pos) - (m & (~(0xffffffff - ((1 << pos) - 1)))) < gap)) 7 pos ++; 8 return n & (0xffffffff - ((1 << pos) - 1)); 9 } Repeated DNA Sequences All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example: ACGAATTCCG. When studying DNA, it is sometimes useful to identify repeated sequences within the DNA. Write a function to find all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule. (leetcode 187) 举例 : Given s = AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT, Return: [ AAAAACCCCC, CCCCCAAAAA ]. Hash+bit : 时间复杂度 O(n), 空间复杂度 O(n) 这题一般会想到使用 hash 来统计是否重复, 问题是 hash 的 key 值怎么表示才是体现水准的关键, 笔者一开始想到的就是把 A,C,G,T 分别编号问 0,1,2,3 然后将序列化成 4 进制的数. 但是还有一种非常巧妙的做法, 它的原理如下 :

194 16.3 相关问题 185 A,C,G,T 的 Asci 码分别是 0x41, 0x43, 0x47 和 0x54. 这几个数的二进制表示中倒数第三和倒数第二位分别是 00, 01, 11 和 10, 所以可以利用这两位产生 key 1 vector < string > findrepeateddnasequences( string s) { 2 vector<string> result; 3 unordered_map<int, int> table; 4 for(int i = 0; i + 9 < s.size(); i++){ 5 unsigned int code = 0x0; 6 for(int j = 0; j < 10; j++) 7 code = (((s[i+j] & 0x6) >> 1) << (2*j)); 8 if(table[code] == 1) result.push_back(s.substr(i,10)); 9 table[code]++; 10 } 11 return result; 12 }

195 第 17 章数学 17.1 基本概念 TODO 186

196 17.2 经典问题 经典问题 Gcd Given you two integer, return the greatest common divisor of them. 欧几里得算法 : 时间复杂度 O(1), 空间复杂度 O(1) 欧几里得算法又称辗转相除法. 假设 a = b, 那么 gcd(a,b) = gcd(a mod b,b) 直到 a == 0 或者 b == 0 时候另一个非零值就是 gcd(a,b) 1 //assume a >= b 2 unsigned int gcd(unsigned int a, unsigned int b){ 3 if (b == 0) return a; 4 return gcd(b, a % b); 5 } Lcm Given you two integer, return the lowwest common multiple of them. 欧几里得算法 : 时间复杂度 O(1), 空间复杂度 O(1) 我们有结论 lcm(a.b) = a*b / gcd(a, b), 所以实质上还是求 gcd 1 //assume a >= b 2 unsigned int gcd(unsigned int a, unsigned int b){ 3 if (b == 0) return a; 4 return gcd(b, a % b); 5 } 6 unsigned int lcm(unsigned int a, unsigned int b){ 7 if(a < b) return lcm(b, a); 8 return a / gcd(a, b) * b; 9 } Prime Number Given you one positive integer, determine if it is a prime number. Eratosthenes 筛法 : 时间复杂度 O(???), 空间复杂度 O(1) 这里介绍的是使用素数表筛选判断整数 n 是否是素数, 那么问题来了 : 怎么构造素数表呢? 这里就是采用边筛边构造素数表的方法. 从素数表初始化集合只有元素 2, 然后使用 2 筛掉 [3,n] 区间中的所有 2 的倍数, 然后判断 3 是否被筛掉, 如果没有则将 3 加入素数表, 继续新一轮筛 ; 如果被筛掉了, 则继续判断 4,5... 直到 n 1 #define MAXN /** prime_table[i]==1 表示 i 是素数等于, 0 则不是素数 */ 4 int prime_table[maxn+1]; 5 6 void compute_ prime_ table() { 7 int i, j; 8 const int upper = sqrt(maxn); 9 for (i = 2; i <= MAXN; i++)

197 17.2 经典问题 prime_table[i] = 1; 11 prime_table[0] = 0; 12 prime_table[1] = 0; 13 for (i = 2; i < upper; i++) 14 if(prime_table[i]) { 15 for (j = 2; j * i <= MAXN; j++) 16 prime_table[j*i] = 0; 17 } 18 } int is_prime(unsigned int n) { 21 return prime_table[n]; 22 } Next Permutation Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). The replacement must be in-place, do not allocate extra memory. (leetcode 31) 举例 : Inputs are in the left-hand column and its corresponding outputs are in the right-hand column. 1,2,3 1,3,2 3,2,1 1,2,3 1,1,5 1,5,1 组合数学 : 时间复杂度 O(n), 空间复杂度 O(1) 这是组合数学中的经典问题, 假设我们现在的数是 a 1,a 2,...a n, 我们找该序列最后一个极大点, 设为 a k, 我们知道 a k 1 <a k, a k >a k+1 >...>a n, 另外设 a m 是 a k,...,a n 中大于 a k 1 的最小的那个, 那么下一个数就是 a 1,a 2,...a m,a k,a k+1,...,a m 1,a k 1,a m+1,...,a n 1 void nextpermutation(vector<int> &num) { 2 int pos = num.size() - 1; 3 while(pos > 0 && num[pos - 1] >= num[pos]) pos--; 4 if(pos!= 0){ 5 int pre = pos - 1; 6 auto index = lower_bound(num.begin() + pos, num.end(), num[pre], greater<int>()); 7 swap(num[pre], num[index - num.begin() - 1]); 8 } 9 reverse(num.begin() + pos, num.end()); 10 }

198 17.3 相关问题 相关问题 Reverse Integer Reverse digits of an integer. (leetcode 7) 举例 : Example1: x = 123, return 321 Example2: x = -123, return -321 Note: Here are some good questions to ask before coding. Bonus points for you if you have already thought through this! If the integer s last digit is 0, what should the output be? ie, cases such as 10, 100. Did you notice that the reversed integer might overflow? Assume the input is a 32-bit integer, then the reverse of overflows. How should you handle such cases? For the purpose of this problem, assume that your function returns 0 when the reversed integer overflows. Math : 时间复杂度 O(1), 空间复杂度 O(1) 这题有两个点需要注意, 一个是正数与负数怎么表示? 我这里采用全化为负数处理, 简单明了 ; 另一个问题就是溢出预先判断, 可以在乘之前判断一下 1 int reverse (int x) { 2 if(x > 0) return -reverse(-x); 3 int rever = 0; 4 while(x!= 0){ 5 if(rever < (INT_MIN - x%10) / 10) 6 return 0; 7 rever = rever * 10 + x % 10; 8 x = x / 10; 9 } 10 return rever; 11 } Palindrome Number Determine whether an integer is a palindrome. Do this without extra space. (leetcode 9) Math : 时间复杂度 O(1), 空间复杂度 O(1) 我们可以只反转低半部分, 然后判断反转的低半部分是否与高半部分相符即可 1 bool ispalindrome(int x) { 2 if(x < 0 (x!= 0 && x % 10 == 0)) return false; 3 int rev = 0; 4 while(x > rev){ 5 rev = rev * 10 + (x % 10); 6 x = x / 10;

199 17.3 相关问题 } 8 return (x == rev) (x == rev/10); 9 } Multiply Strings Given two numbers represented as strings, return multiplication of the numbers as a string. (leetcode 43) Note: Note: The numbers can be arbitrarily large and are non-negative. 大数 : 时间复杂度 O(n*m), 空间复杂度 O(n*m) 这题是典型的大数问题, 对于大数问题一般采用 vector 或者 string 来存储大数, 需要注意高低位的顺序, 其运算方式完全按照小学学习的加减乘除计算方法计算就可以了. 1 // num1 为低位 [0],num2 为低位 [0], num1 = num1 + (num2 << left) 2 void bigadd(string &num1, string &num2, int left){ 3 string fill = string(left, 0 ); 4 num2 = fill + num2; 5 int cin = 0; 6 for(int i = 0; i < num1.size(); i++){ 7 int out = cin + (num1[i] - 0 ) + (num2[i] - 0 ); 8 num1[i] = (out % 10) + 0 ; 9 cin = out / 10; 10 } 11 } // num1 为低位 [0] 14 void bigmul(string &result, const string &num1, const char num2){ 15 int cin = 0; 16 int mul = num2-0 ; 17 for(int i = 0; i < num1.size(); i++){ 18 int out = cin + mul * (num1[i] - 0 ); 19 result[i] = (out % 10) + 0 ; 20 cin = out / 10; 21 } 22 if(cin) result[num1.size()] = cin + 0 ; 23 } string multiply(string num1, string num2) { 26 int n1 = num1.size(), n2 = num2.size(); 27 if(n1 < n2) return multiply(num2, num1); 28 reverse(num1.begin(), num1.end()); 29 reverse(num2.begin(), num2.end()); 30 int n = n1 + n2; 31 vector<string> line(n2, string(n, 0 )); 32 string result(n, 0 ); 33 for(int i = 0; i < n2; i++){ 34 bigmul(line[i], num1, num2[i]); 35 bigadd(result, line[i], i); 36 } 37 reverse(result.begin(), result.end()); 38 int nozero = 0; 39 while(nozero < result.size() && result[nozero] == 0 ) 40 nozero ++;

200 17.3 相关问题 return nozero == result.size()? "0" : result.substr(nozero, result. size() - nozero); 42 } Plus One Given a non-negative number represented as an array of digits, plus one to the number. The digits are stored such that the most significant digit is at the head of the list. (leetcode 66) 大数 : 时间复杂度 O(n), 空间复杂度 O(n) 典型的大数问题 1 vector<int> plusone(vector<int> &digits) { 2 int cin = 1, n = digits.size(); 3 vector<int> result; 4 for(int i = n - 1; i >= 0; i--){ 5 result.push_back((digits[i] + cin) % 10); 6 cin = (digits[i] + cin) / 10; 7 } 8 if(cin) result.push_back(1); 9 reverse(result.begin(), result.end()); 10 return result; 11 } Max Points on a Line Given n points on a 2D plane, find the maximum number of points that lie on the same straight line. (leetcode 149) Hash+Gcd : 时间复杂度 O(n 2 ), 空间复杂度 O(n) 这题思路就是选取每个点作为直线上的点, 然后计算其他点与该点形成的斜率, 斜率一样的说明这些点和该点在同一条直线上. 问题就编程了怎么记录这些斜率一样的信息, 我们想到使用 hash 记录, 那么这个斜率自然就是 key 值, 这个 key 值不能是整数, 也不能是浮点数 ( 会有误差 ), 只能用最简分数表示, 如何化简分数那就使用 gcd 1 //x, 最大公约数, 如果或为 yxy0 返回, 或 yx 2 int gcd(int x, int y){ 3 x = abs(x); 4 y = abs(y); 5 while(x && y){ 6 if(x < y) y = y - x; 7 else x = x - y; 8 } 9 return x!= 0? x : y; 10 } struct hashkey{ 13 std::size_t operator()(const pair<int,int> &x) const{ 14 using std::hash; 15 return x.first ^ x.second; 16 }

201 17.3 相关问题 }; //pair<int,int 表示斜率 >,(0,0) 表示重点,(0,1) 表示平行与轴 y,(1,0) 表示平行于轴 x 20 int maxpoints(vector<point> &points) { 21 int result = 0; 22 for(int i = 0; i < points.size(); i++){ 23 unordered_map<pair<int,int>, int, hashkey> table; 24 int cur = 0, base = 0; 25 for(int j = 0; j < points.size(); j++){ 26 int dx = points[j].x - points[i].x; 27 int dy = points[j].y - points[i].y; 28 if(dx == 0 && dy == 0){ 29 base ++; 30 continue; 31 } 32 pair<int, int> k(make_pair(dx/gcd(dx, dy), dy/gcd(dx,dy))); 33 if(table.count(k)) table[k]++; 34 else table[k] = 1; 35 } 36 for(auto iter = table.begin(); iter!= table.end(); iter++) 37 if(cur < iter->second) cur = iter->second; 38 result = max(result, base + cur); 39 } 40 return result; 41 } Fraction to Recurring Decimal Given two integers representing the numerator and denominator of a fraction, return the fraction in string format. If the fractional part is repeating, enclose the repeating part in parentheses. (leetcode 166) 举例 : Given numerator = 1, denominator = 2, return 0.5. Given numerator = 2, denominator = 1, return 2. Given numerator = 2, denominator = 3, return 0.(6). Math+Hash : 时间复杂度 O(1), 空间复杂度 O(1) 注意正负号, 注意溢出, 注意如何判断是循环小数 ( 采用 hash) 1 string num2str(int num){ 2 if(num == 0) return "0"; 3 if(num < 0) return "-" + num2str(-num); 4 string result = ""; 5 while(num){ 6 result.push_back((num % 10) + 0 ); 7 num /= 10; 8 } 9 reverse(result.begin(), result.end()); 10 return result; 11 } struct hashpair{ 14 size_t operator()(const pair<int, int> key) const{ 15 return key.first ^ key.second;

202 17.3 相关问题 } 17 }; string fractiontodecimal(int numerator, int denominator) { 20 string result; 21 if(denominator == 0) return result; 22 if(numerator == 0) return "0"; 23 bool ismunis = true; 24 if((numerator < 0 && denominator < 0) (numerator > 0 && denominator > 0)){ 25 ismunis = false; 26 } 27 numerator = numerator < 0? numerator : -numerator; 28 denominator = denominator < 0? denominator : - denominator; 29 if(numerator == INT_MIN && denominator == -1){ 30 result = " "; 31 numerator = 0; 32 } 33 else{ 34 int ret = 0; 35 while( numerator <= denominator){ 36 ret = ret + numerator / denominator; 37 numerator = numerator - denominator * ( numerator / denominator); 38 } 39 result = num2str(ret); 40 } 41 if(numerator){ 42 result.push_back(. ); 43 int pos = result.size() - 1; 44 unordered_map<pair<int, int>, int, hashpair> table; 45 double numerator_ d = numerator, denominator_ d = denominator; 46 while(int(numerator_d)){ 47 if(table.count(make_pair(int(numerator_d), int(denominator_d )))){ 48 int index = table[make_pair(int(numerator_d), int( denominator_d))]; 49 result.insert(index, 1, ( ); 50 result.push_back( ) ); 51 break; 52 } 53 pos ++; 54 table[make_pair(int(numerator_d), int(denominator_d))] = pos ; 55 numerator_ d *= 10; 56 int cur = numerator_d / denominator_d; 57 numerator_d = numerator_d - denominator_d * cur; 58 result.push_back( 0 + cur); 59 } 60 } 61 return ismunis? "-" + result : result; 62 } Excel Sheet Column Title Given a positive integer, return its corresponding column title as appear in an Excel sheet. (leetcode 168)

203 17.3 相关问题 194 举例 : 1 A 2 B 3 C Z 27 AA 28 AB 递归 : 时间复杂度 O(n), 空间复杂度 O(1) 这是典型的进制转换问题, 可以采用递归实现一行代码解决 1 string converttotitle(int n) { 2 return n <= 0? "" : converttotitle((n-1)/26) + string(1,((n-1) % 26) + A ); 3 } Excel Sheet Column Number Related to question Excel Sheet Column Title Given a column title as appear in an Excel sheet, return its corresponding column number. (leetcode 171) 举例 : A 1 B 2 C 3... Z 26 AA 27 AB 28 递归 : 时间复杂度 O(n), 空间复杂度 O(1) 进制转换, 使用递归一行代码解决 1 int titletonumber(string s) { 2 return s.size()? titletonumber(s.substr(0, s.size() - 1))*26 + s[s. size()-1] - A + 1: 0; 3 } Factorial Trailing Zeroes Given an integer n, return the number of trailing zeroes in n!. (leetcode 172) Note: Note: Your solution should be in logarithmic time complexity. Math : 时间复杂度 O(lg 5 n), 空间复杂度 O(1) 这题其实就是统计 n 中包含 5 因子的个数, 因为 2 因子个数肯定比 5 的多, 所以它们组合成 10 的倍数

204 17.3 相关问题 int trailingzeroes(int n) { 2 return abs(n) < 5? 0 : abs(n/5) + trailingzeroes(n/5); 3 } Happy Number Write an algorithm to determine if a number is happy. A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers. (leetcode 202) 举例 : 19 is a happy number = = = = 1 Hash : 时间复杂度 O(1), 空间复杂度 O(1) 使用 hash 记录是否重现 1 int change(int n){ 2 return n == 0? 0 : (n % 10) * (n % 10) + change(n/10); 3 } 4 5 bool ishappy(int n) { 6 unordered_set<int> table; 7 while(n!= 1){ 8 table.insert(n); 9 n = change(n); 10 if(table.count(n)) 11 return false; 12 } 13 return true; 14 } Count Primes Count the number of prime numbers less than a non-negative number, n. (leetcode 204) 筛选法 : 时间复杂度 O(n), 空间复杂度 O(m) 采用前面提到的筛选法即可 1 int countprimes(int n) { 2 if(n < 3) return 0; 3 vector<bool> table(n+1, true); 4 int pos = 2, count = 0;

205 17.3 相关问题 while(pos < n){ 6 while(pos < n &&!table[pos]) pos++; 7 if(pos < n) count++; 8 for(int i = 2; i * pos < n; i++) 9 table[i*pos] = false; 10 pos ++; 11 } 12 return count; 13 }

LEETCODE leetcode.com 一 个 在 线 编 程 网 站, 收 集 了 IT 公 司 的 面 试 题, 包 括 算 法, 数 据 库 和 shell 算 法 题 支 持 多 种 语 言, 包 括 C, C++, Java, Python 等 2015 年 3 月 份 加 入 了 R

LEETCODE leetcode.com 一 个 在 线 编 程 网 站, 收 集 了 IT 公 司 的 面 试 题, 包 括 算 法, 数 据 库 和 shell 算 法 题 支 持 多 种 语 言, 包 括 C, C++, Java, Python 等 2015 年 3 月 份 加 入 了 R 用 RUBY 解 LEETCODE 算 法 题 RUBY CONF CHINA 2015 By @quakewang LEETCODE leetcode.com 一 个 在 线 编 程 网 站, 收 集 了 IT 公 司 的 面 试 题, 包 括 算 法, 数 据 库 和 shell 算 法 题 支 持 多 种 语 言, 包 括 C, C++, Java, Python 等 2015 年 3 月 份

More information

C/C++ - 字符输入输出和字符确认

C/C++ - 字符输入输出和字符确认 C/C++ Table of contents 1. 2. getchar() putchar() 3. (Buffer) 4. 5. 6. 7. 8. 1 2 3 1 // pseudo code 2 read a character 3 while there is more input 4 increment character count 5 if a line has been read,

More information

C/C++ - 函数

C/C++ - 函数 C/C++ Table of contents 1. 2. 3. & 4. 5. 1 2 3 # include # define SIZE 50 int main ( void ) { float list [ SIZE ]; readlist (list, SIZE ); sort (list, SIZE ); average (list, SIZE ); bargragh

More information

Microsoft PowerPoint - Lecture7II.ppt

Microsoft PowerPoint - Lecture7II.ppt Lecture 8II SUDOKU PUZZLE SUDOKU New Play Check 軟體實作與計算實驗 1 4x4 Sudoku row column 3 2 } 4 } block 1 4 軟體實作與計算實驗 2 Sudoku Puzzle Numbers in the puzzle belong {1,2,3,4} Constraints Each column must contain

More information

穨control.PDF

穨control.PDF TCP congestion control yhmiu Outline Congestion control algorithms Purpose of RFC2581 Purpose of RFC2582 TCP SS-DR 1998 TCP Extensions RFC1072 1988 SACK RFC2018 1996 FACK 1996 Rate-Halving 1997 OldTahoe

More information

Fun Time (1) What happens in memory? 1 i n t i ; 2 s h o r t j ; 3 double k ; 4 char c = a ; 5 i = 3; j = 2; 6 k = i j ; H.-T. Lin (NTU CSIE) Referenc

Fun Time (1) What happens in memory? 1 i n t i ; 2 s h o r t j ; 3 double k ; 4 char c = a ; 5 i = 3; j = 2; 6 k = i j ; H.-T. Lin (NTU CSIE) Referenc References (Section 5.2) Hsuan-Tien Lin Deptartment of CSIE, NTU OOP Class, March 15-16, 2010 H.-T. Lin (NTU CSIE) References OOP 03/15-16/2010 0 / 22 Fun Time (1) What happens in memory? 1 i n t i ; 2

More information

Open topic Bellman-Ford算法与负环

Open topic   Bellman-Ford算法与负环 Open topic Bellman-Ford 2018 11 5 [email protected] 1/15 Contents 1. G s BF 2. BF 3. BF 2/15 BF G Bellman-Ford false 3/15 BF G Bellman-Ford false G c = v 0, v 1,..., v k (v 0 = v k ) k w(v i 1,

More information

Computer Architecture

Computer Architecture ECE 3120 Computer Systems Assembly Programming Manjeera Jeedigunta http://blogs.cae.tntech.edu/msjeedigun21 Email: [email protected] Tel: 931-372-6181, Prescott Hall 120 Prev: Basic computer concepts

More information

Microsoft Word - template.doc

Microsoft Word - template.doc HGC efax Service User Guide I. Getting Started Page 1 II. Fax Forward Page 2 4 III. Web Viewing Page 5 7 IV. General Management Page 8 12 V. Help Desk Page 13 VI. Logout Page 13 Page 0 I. Getting Started

More information

Improved Preimage Attacks on AES-like Hash Functions: Applications to Whirlpool and Grøstl

Improved Preimage Attacks on AES-like Hash Functions: Applications to Whirlpool and Grøstl SKLOIS (Pseudo) Preimage Attack on Reduced-Round Grøstl Hash Function and Others Shuang Wu, Dengguo Feng, Wenling Wu, Jian Guo, Le Dong, Jian Zou March 20, 2012 Institute. of Software, Chinese Academy

More information

C/C++语言 - 运算符、表达式和语句

C/C++语言 - 运算符、表达式和语句 C/C++ Table of contents 1. 2. 3. 4. C C++ 5. 6. 7. 1 i // shoe1.c: # include # define ADJUST 7. 64 # define SCALE 0. 325 int main ( void ) { double shoe, foot ; shoe = 9. 0; foot = SCALE * shoe

More information

C/C++ - 文件IO

C/C++ - 文件IO C/C++ IO Table of contents 1. 2. 3. 4. 1 C ASCII ASCII ASCII 2 10000 00100111 00010000 31H, 30H, 30H, 30H, 30H 1, 0, 0, 0, 0 ASCII 3 4 5 UNIX ANSI C 5 FILE FILE 6 stdio.h typedef struct { int level ;

More information

coverage2.ppt

coverage2.ppt Satellite Tool Kit STK/Coverage STK 82 0715 010-68745117 1 Coverage Definition Figure of Merit 2 STK Basic Grid Assets Interval Description 3 Grid Global Latitude Bounds Longitude Lines Custom Regions

More information

<4D6963726F736F667420506F776572506F696E74202D20B5DAD2BBD5C228B4F2D3A1B0E6292E707074205BBCE6C8DDC4A3CABD5D>

<4D6963726F736F667420506F776572506F696E74202D20B5DAD2BBD5C228B4F2D3A1B0E6292E707074205BBCE6C8DDC4A3CABD5D> Homeworks ( 第 三 版 ):.4 (,, 3).5 (, 3).6. (, 3, 5). (, 4).4.6.7 (,3).9 (, 3, 5) Chapter. Number systems and codes 第 一 章. 数 制 与 编 码 . Overview 概 述 Information is of digital forms in a digital system, and

More information

演算法導入、ソート、データ構造、ハッシュ

演算法導入、ソート、データ構造、ハッシュ 培訓 - 1 演算法導入 ソート データ構造 ハッシュ 演算法導入 ソート データ構造 ハッシュ momohuang c2251393 chiangyo September 23, 2013 1 Schedule of the Year 1.1 Major Competition 9 12 11 10 12 10 TOI 的最 3 TOI 3 TOI 100 20 4 TOI 30 12 5 TOI

More information

K301Q-D VRT中英文说明书141009

K301Q-D VRT中英文说明书141009 THE INSTALLING INSTRUCTION FOR CONCEALED TANK Important instuction:.. Please confirm the structure and shape before installing the toilet bowl. Meanwhile measure the exact size H between outfall and infall

More information

Microsoft Word - Final Exam Review Packet.docx

Microsoft Word - Final Exam Review Packet.docx Do you know these words?... 3.1 3.5 Can you do the following?... Ask for and say the date. Use the adverbial of time correctly. Use Use to ask a tag question. Form a yes/no question with the verb / not

More information

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM CHAPTER 6 SQL SQL SQL 6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM 3. 1986 10 ANSI SQL ANSI X3. 135-1986

More information

Fuzzy Highlight.ppt

Fuzzy Highlight.ppt Fuzzy Highlight high light Openfind O(kn) n k O(nm) m Knuth O(n) m Knuth Unix grep regular expression exact match Yahoo agrep fuzzy match Gais agrep Openfind gais exact match fuzzy match fuzzy match O(kn)

More information

ENGG1410-F Tutorial 6

ENGG1410-F Tutorial 6 Jianwen Zhao Department of Computer Science and Engineering The Chinese University of Hong Kong 1/16 Problem 1. Matrix Diagonalization Diagonalize the following matrix: A = [ ] 1 2 4 3 2/16 Solution The

More information

第1章 簡介

第1章 簡介 EAN.UCCThe Global Language of Business 4 512345 678906 > 0 12345 67890 5 < > 1 89 31234 56789 4 ( 01) 04601234567893 EAN/UCC-14: 15412150000151 EAN/UCC-13: 5412150000161 EAN/UCC-14: 25412150000158 EAN/UCC-13:

More information

Microsoft PowerPoint - STU_EC_Ch08.ppt

Microsoft PowerPoint - STU_EC_Ch08.ppt 樹德科技大學資訊工程系 Chapter 8: Counters Shi-Huang Chen Fall 2010 1 Outline Asynchronous Counter Operation Synchronous Counter Operation Up/Down Synchronous Counters Design of Synchronous Counters Cascaded Counters

More information

Microsoft Word - 981192001.htm

Microsoft Word - 981192001.htm 098 年 度 11901 電 腦 軟 體 設 計 (JAVA) 乙 級 技 術 士 技 能 檢 定 學 科 測 試 試 題 本 試 卷 有 選 擇 題 80 題, 每 題 1.25 分, 皆 為 單 選 選 擇 題, 測 試 時 間 為 100 分 鐘, 請 在 答 案 卡 上 作 答, 答 錯 不 倒 扣 ; 未 作 答 者, 不 予 計 分 准 考 證 號 碼 : 姓 名 : 單 選 題 :

More information

204 */ InitiateStack s ; /* s */ i = n; t = p = new node; /* */ p->data = postorder[i]; while i > q = new node; if parent[i - ] == postorder[i] S,T S

204 */ InitiateStack s ; /* s */ i = n; t = p = new node; /* */ p->data = postorder[i]; while i > q = new node; if parent[i - ] == postorder[i] S,T S 28 4 Vol.28 No.4 4 204 2 JOURNAL OF NANTONG VOCATIONAL UNIVERSITY Dec. 204!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! doi:0.3969/j.issn.008-5327.204.04.024 唐自立 ( 苏州大学计算机科学与技术学院, 江苏苏州 25006)

More information

2015年4月11日雅思阅读预测机经(新东方版)

2015年4月11日雅思阅读预测机经(新东方版) 剑 桥 雅 思 10 第 一 时 间 解 析 阅 读 部 分 1 剑 桥 雅 思 10 整 体 内 容 统 计 2 剑 桥 雅 思 10 话 题 类 型 从 以 上 统 计 可 以 看 出, 雅 思 阅 读 的 考 试 话 题 一 直 广 泛 多 样 而 题 型 则 稳 中 有 变 以 剑 桥 10 的 test 4 为 例 出 现 的 三 篇 文 章 分 别 是 自 然 类, 心 理 研 究 类,

More information

BC04 Module_antenna__ doc

BC04 Module_antenna__ doc http://www.infobluetooth.com TEL:+86-23-68798999 Fax: +86-23-68889515 Page 1 of 10 http://www.infobluetooth.com TEL:+86-23-68798999 Fax: +86-23-68889515 Page 2 of 10 http://www.infobluetooth.com TEL:+86-23-68798999

More information

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO Car DVD New GUI IR Flow User Manual V0.1 Jan 25, 2008 19, Innovation First Road Science Park Hsin-Chu Taiwan 300 R.O.C. Tel: 886-3-578-6005 Fax: 886-3-578-4418 Web: www.sunplus.com Important Notice SUNPLUS

More information

untitled

untitled Co-integration and VECM Yi-Nung Yang CYCU, Taiwan May, 2012 不 列 1 Learning objectives Integrated variables Co-integration Vector Error correction model (VECM) Engle-Granger 2-step co-integration test Johansen

More information

什么是函数式编程?

什么是函数式编程? 函数式编程 FUNCTIONAL PROGRAMMING [email protected] 什么是函数式编程? 真相是 从停机问题开始 Bug 假设有停机判定算法 function halting(func, input) { } return if_func_will_halt_on_input; 充分利用停机判定 function ni_ma(func) { if (halting(func,

More information

C/C++ - 字符串与字符串函数

C/C++ - 字符串与字符串函数 C/C++ Table of contents 1. 2. 3. 4. 1 char C 2 char greeting [50] = " How " " are " " you?"; char greeting [50] = " How are you?"; 3 printf ("\" Ready, go!\" exclaimed John."); " Ready, go!" exclaimed

More information

C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 5 月 3 日 1

C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 5 月 3 日 1 C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 月 3 日 1 1 INPUTOUTPUT 1 InputOutput 题目描述 用 cin 输入你的姓名 ( 没有空格 ) 和年龄 ( 整数 ), 并用 cout 输出 输入输出符合以下范例 输入 master 999 输出 I am master, 999 years old. 注意 "," 后面有一个空格,"." 结束,

More information

C/C++程序设计 - 字符串与格式化输入/输出

C/C++程序设计 - 字符串与格式化输入/输出 C/C++ / Table of contents 1. 2. 3. 4. 1 i # include # include // density of human body : 1. 04 e3 kg / m ^3 # define DENSITY 1. 04 e3 int main ( void ) { float weight, volume ; int

More information

WWW PHP

WWW PHP WWW PHP 2003 1 2 function function_name (parameter 1, parameter 2, parameter n ) statement list function_name sin, Sin, SIN parameter 1, parameter 2, parameter n 0 1 1 PHP HTML 3 function strcat ($left,

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: [email protected] 49 [P.51] C/C++ [P.52] [P.53] [P.55] (int) [P.57] (float/double) [P.58] printf scanf [P.59] [P.61] ( / ) [P.62] (char) [P.65] : +-*/% [P.67] : = [P.68] : ,

More information

目 錄 壹 青 輔 會 結 案 附 件 貳 活 動 計 劃 書 參 執 行 內 容 一 教 學 內 容 二 與 當 地 教 師 教 學 交 流 三 服 務 執 行 進 度 肆 執 行 成 效 一 教 學 課 程 二 與 當 地 教 師 教 學 交 流 三 服 務 滿 意 度 調 查 伍 服 務 檢

目 錄 壹 青 輔 會 結 案 附 件 貳 活 動 計 劃 書 參 執 行 內 容 一 教 學 內 容 二 與 當 地 教 師 教 學 交 流 三 服 務 執 行 進 度 肆 執 行 成 效 一 教 學 課 程 二 與 當 地 教 師 教 學 交 流 三 服 務 滿 意 度 調 查 伍 服 務 檢 2 0 1 0 年 靜 宜 青 年 國 際 志 工 泰 北 服 務 成 果 報 告 指 導 單 位 : 行 政 院 青 年 輔 導 委 員 會 僑 務 委 員 會 主 辦 單 位 : 靜 宜 大 學 服 務 學 習 發 展 中 心 協 力 單 位 : 靜 宜 大 學 師 資 培 育 中 心 財 團 法 人 台 灣 明 愛 文 教 基 金 會 中 華 民 國 九 十 九 年 九 月 二 十 四 日 目

More information

四川省普通高等学校

四川省普通高等学校 四 川 省 普 通 高 等 学 校 计 算 机 应 用 知 识 和 能 力 等 级 考 试 考 试 大 纲 (2013 年 试 行 版 ) 四 川 省 教 育 厅 计 算 机 等 级 考 试 中 心 2013 年 1 月 目 录 一 级 考 试 大 纲 1 二 级 考 试 大 纲 6 程 序 设 计 公 共 基 础 知 识 6 BASIC 语 言 程 序 设 计 (Visual Basic) 9

More information

Guide to Install SATA Hard Disks

Guide to Install SATA Hard Disks SATA RAID 1. SATA. 2 1.1 SATA. 2 1.2 SATA 2 2. RAID (RAID 0 / RAID 1 / JBOD).. 4 2.1 RAID. 4 2.2 RAID 5 2.3 RAID 0 6 2.4 RAID 1.. 10 2.5 JBOD.. 16 3. Windows 2000 / Windows XP 20 1. SATA 1.1 SATA Serial

More information

AN INTRODUCTION TO PHYSICAL COMPUTING USING ARDUINO, GRASSHOPPER, AND FIREFLY (CHINESE EDITION ) INTERACTIVE PROTOTYPING

AN INTRODUCTION TO PHYSICAL COMPUTING USING ARDUINO, GRASSHOPPER, AND FIREFLY (CHINESE EDITION ) INTERACTIVE PROTOTYPING AN INTRODUCTION TO PHYSICAL COMPUTING USING ARDUINO, GRASSHOPPER, AND FIREFLY (CHINESE EDITION ) INTERACTIVE PROTOTYPING 前言 - Andrew Payne 目录 1 2 Firefly Basics 3 COMPONENT TOOLBOX 目录 4 RESOURCES 致谢

More information

Microsoft PowerPoint - ch6 [相容模式]

Microsoft PowerPoint - ch6 [相容模式] UiBinder [email protected] UiBinder Java GWT UiBinder XML UI i18n (widget) 1 2 UiBinder HelloWidget.ui.xml: UI HelloWidgetBinder HelloWidget.java XML UI Owner class ( Composite ) UI XML UiBinder: Owner

More information

Microsoft PowerPoint - string_kruse [兼容模式]

Microsoft PowerPoint - string_kruse [兼容模式] Strings Strings in C not encapsulated Every C-string has type char *. Hence, a C-string references an address in memory, the first of a contiguous set of bytes that store the characters making up the string.

More information

高中英文科教師甄試心得

高中英文科教師甄試心得 高 中 英 文 科 教 師 甄 試 心 得 英 語 學 系 碩 士 班 林 俊 呈 高 雄 市 立 高 雄 高 級 中 學 今 年 第 一 次 參 加 教 師 甄 試, 能 夠 在 尚 未 服 兵 役 前 便 考 上 高 雄 市 立 高 雄 高 級 中 學 專 任 教 師, 自 己 覺 得 很 意 外, 也 很 幸 運 考 上 後 不 久 在 與 雄 中 校 長 的 會 談 中, 校 長 的 一 句

More information

Microsoft Word - 09.數學136-281.docx

Microsoft Word - 09.數學136-281.docx 136. 計 算 梯 型 面 積 (1 分 ) 請 以 JAVA 運 算 式 計 算 下 面 梯 形 面 積, 並 輸 出 面 積 結 果 梯 形 面 積 公 式 為 :( 上 底 + 下 底 ) 高 2 每 一 組 依 序 分 別 輸 入 梯 形 的 上 底 下 底 及 高 的 整 數 輸 出 梯 形 面 積 輸 入 輸 出 94 190 120 99 54 47 137. 計 算 三 角 形 面

More information

GCSE Mathematics Question Paper Unit 2 March 2012

GCSE Mathematics Question Paper Unit 2 March 2012 Centre Number Surname Candidate Number For Examiner s Use Other Names Candidate Signature Examiner s Initials General Certificate of Secondary Education Higher Tier March 2012 Pages 2 3 4 5 Mark Mathematics

More information

ebook39-5

ebook39-5 5 3 last-in-first-out, LIFO 3-1 L i n e a r L i s t 3-8 C h a i n 3 3. 8. 3 C + + 5.1 [ ] s t a c k t o p b o t t o m 5-1a 5-1a E D 5-1b 5-1b E E 5-1a 5-1b 5-1c E t o p D t o p D C C B B B t o p A b o

More information

VASP应用运行优化

VASP应用运行优化 1 VASP [email protected] April 8, 2018 Contents 1 2 2 2 3 2 4 2 4.1........................................................ 2 4.2..................................................... 3 5 4 5.1..........................................................

More information

Preface This guide is intended to standardize the use of the WeChat brand and ensure the brand's integrity and consistency. The guide applies to all d

Preface This guide is intended to standardize the use of the WeChat brand and ensure the brand's integrity and consistency. The guide applies to all d WeChat Search Visual Identity Guidelines WEDESIGN 2018. 04 Preface This guide is intended to standardize the use of the WeChat brand and ensure the brand's integrity and consistency. The guide applies

More information

Microsoft Word - C-pgm-ws2010.doc

Microsoft Word - C-pgm-ws2010.doc Information and Communication Technology 資訊與通訊科技 Loops (while/for) C 廻路 姓名 : 班別 : ( ) CS C Programming #1 Functions 函數 : 1 若 n=14, 求以下表示式的值 Expressions 表示式 Value 值 Expressions 表示式 Value 值 A 20 2 * (n /

More information

C/C++语言 - C/C++数据

C/C++语言 - C/C++数据 C/C++ C/C++ Table of contents 1. 2. 3. 4. char 5. 1 C = 5 (F 32). 9 F C 2 1 // fal2cel. c: Convert Fah temperature to Cel temperature 2 # include < stdio.h> 3 int main ( void ) 4 { 5 float fah, cel ;

More information

pdf

pdf THE INSTLLING INSTRUCTION FOR CONCELED TNK Important instuction:.. Please confirm the structure and shape before installing the toilet bowl. Meanwhile measure the exact size H between outfall and infall

More information

Untitled-3

Untitled-3 SEC.. Separable Equations In each of problems 1 through 8 solve the given differential equation : ü 1. y ' x y x y, y 0 fl y - x 0 fl y - x 0 fl y - x3 3 c, y 0 ü. y ' x ^ y 1 + x 3 x y 1 + x 3, y 0 fl

More information

Explain each of the following terms. (12%) (a) O(n 2 ) (b) protected in C++ language (c) sparse matrix 7. Write

Explain each of the following terms. (12%) (a) O(n 2 ) (b) protected in C++ language (c) sparse matrix 7. Write Department of Computer Science and Engineering National Sun Yat-sen University Data Structures - Middle Exam, Nov. 20, 2017 1. Suppose an array is declared as a[5][6][4], where the address of a[0][0][0]

More information

C/C++ 语言 - 循环

C/C++ 语言 - 循环 C/C++ Table of contents 7. 1. 2. while 3. 4. 5. for 6. 8. (do while) 9. 10. (nested loop) 11. 12. 13. 1 // summing.c: # include int main ( void ) { long num ; long sum = 0L; int status ; printf

More information

Microsoft Word - ChineseSATII .doc

Microsoft Word - ChineseSATII .doc 中 文 SAT II 冯 瑶 一 什 么 是 SAT II 中 文 (SAT Subject Test in Chinese with Listening)? SAT Subject Test 是 美 国 大 学 理 事 会 (College Board) 为 美 国 高 中 生 举 办 的 全 国 性 专 科 标 准 测 试 考 生 的 成 绩 是 美 国 大 学 录 取 新 生 的 重 要 依

More information

Microsoft Word - 数学软文+数学词汇+数学班广告+水印.docx

Microsoft Word - 数学软文+数学词汇+数学班广告+水印.docx GRE by ( GRE ) GRE ETS ETS ETS GRE OG ETS 200 5 GRE GRE GRE Equilateral triangle Isosceles triangle Equilateral Isosceles GRE GRE 162 163 100 = > < + x 2 x 3 equal to, the same as, is more than less than

More information

PowerPoint Presentation

PowerPoint Presentation TOEFL Practice Online User Guide Revised September 2009 In This Guide General Tips for Using TOEFL Practice Online Directions for New Users Directions for Returning Users 2 General Tips To use TOEFL Practice

More information

3 (s05q6) The diagram shows the velocity-time graph for a lift moving between floors in a building. The graph consists of straight line segments. In t

3 (s05q6) The diagram shows the velocity-time graph for a lift moving between floors in a building. The graph consists of straight line segments. In t Mechnics (9709) M1 Topic 1 : s-t and v-t graph(9) Name: Score: Time: 1 (s03q3) The diagram shows the velocity-time graphs for the motion of two cyclists P and Q, whotravel in the same direction along a

More information

Microsoft PowerPoint - STU_EC_Ch04.ppt

Microsoft PowerPoint - STU_EC_Ch04.ppt 樹德科技大學資訊工程系 Chapter 4: Boolean Algebra and Logic Simplification Shi-Huang Chen Fall 200 Outline Boolean Operations and Expressions Laws and Rules of Boolean Algebra DeMorgan's Theorems Boolean Analysis

More information

C++ 程式設計

C++ 程式設計 C C 料, 數, - 列 串 理 列 main 數串列 什 pointer) 數, 數, 數 數 省 不 不, 數 (1) 數, 不 數 * 料 * 數 int *int_ptr; char *ch_ptr; float *float_ptr; double *double_ptr; 數 (2) int i=3; int *ptr; ptr=&i; 1000 1012 ptr 數, 數 1004

More information

PowerPoint 演示文稿

PowerPoint 演示文稿 数据结构与算法 ( 五 ) 张铭主讲 采用教材 : 张铭, 王腾蛟, 赵海燕编写高等教育出版社,2008. 6 ( 十一五 国家级规划教材 ) http://www.jpk.pku.edu.cn/pkujpk/course/sjjg 第五章 的概念 的抽象数据类型 深度优先搜索 宽度优先搜索 的存储结构 D B A E G C H F I 二叉搜索树 堆与优先队列 Huffman 树及其应用 2 5.2

More information

els0xu_zh_nf_v8.book Page Wednesday, June, 009 9:5 AM ELS-0/0C.8

els0xu_zh_nf_v8.book Page Wednesday, June, 009 9:5 AM ELS-0/0C.8 els0xu_zh_nf_v8.book Page Wednesday, June, 009 9:5 AM ELS-0/0C.8 Yamaha ELS-0/0C..8 LCD ELS-0/0C v. typeu LCD ELS-0/0C typeu / -6 / [SEARCH] / - ZH ELS-0/0C.8 els0xu_zh_nf_v8.book Page Wednesday, June,

More information

2009 Japanese First Language Written examination

2009 Japanese First Language Written examination Victorian Certificate of Education 2009 SUPERVISOR TO ATTACH PROCESSING LABEL HERE STUDENT NUMBER Letter Figures Words JAPANESE FIRST LANGUAGE Written examination Monday 16 November 2009 Reading time:

More information

Lorem ipsum dolor sit amet, consectetuer adipiscing elit

Lorem ipsum dolor sit amet, consectetuer adipiscing elit English for Study in Australia 留 学 澳 洲 英 语 讲 座 Lesson 3: Make yourself at home 第 三 课 : 宾 至 如 归 L1 Male: 各 位 朋 友 好, 欢 迎 您 收 听 留 学 澳 洲 英 语 讲 座 节 目, 我 是 澳 大 利 亚 澳 洲 广 播 电 台 的 节 目 主 持 人 陈 昊 L1 Female: 各 位

More information

2010 Japanese First Language Written examination

2010 Japanese First Language Written examination Victorian Certificate of Education 2010 SUPERVISOR TO ATTACH PROCESSING LABEL HERE STUDENT NUMBER Letter Figures Words JAPANESE FIRST LANGUAGE Written examination Monday 15 November 2010 Reading time:

More information

國立中山大學學位論文典藏.PDF

國立中山大學學位論文典藏.PDF I II III The Study of Factors to the Failure or Success of Applying to Holding International Sport Games Abstract For years, holding international sport games has been Taiwan s goal and we are on the way

More information

SuperMap 系列产品介绍

SuperMap 系列产品介绍 [email protected] 3 / 1 / 16 / John M. Yarbrough: Digital Logic Applications and Design + + 30% 70% 1 CHAPTER 1 Digital Concepts and Number Systems 1.1 Digital and Analog: Basic Concepts P1 1.1 1.1

More information

2009 Korean First Language Written examination

2009 Korean First Language Written examination Victorian Certificate of Education 2009 SUPERVISOR TO ATTACH PROCESSING LABEL HERE STUDENT NUMBER Letter Figures Words KOREAN FIRST LANGUAGE Written examination Tuesday 20 October 2009 Reading time: 2.00

More information

(baking powder) 1 ( ) ( ) 1 10g g (two level design, D-optimal) 32 1/2 fraction Two Level Fractional Factorial Design D-Optimal D

(baking powder) 1 ( ) ( ) 1 10g g (two level design, D-optimal) 32 1/2 fraction Two Level Fractional Factorial Design D-Optimal D ( ) 4 1 1 1 145 1 110 1 (baking powder) 1 ( ) ( ) 1 10g 1 1 2.5g 1 1 1 1 60 10 (two level design, D-optimal) 32 1/2 fraction Two Level Fractional Factorial Design D-Optimal Design 1. 60 120 2. 3. 40 10

More information

Introduction to Hamilton-Jacobi Equations and Periodic Homogenization

Introduction to Hamilton-Jacobi Equations  and Periodic Homogenization Introduction to Hamilton-Jacobi Equations and Periodic Yu-Yu Liu NCKU Math August 22, 2012 Yu-Yu Liu (NCKU Math) H-J equation and August 22, 2012 1 / 15 H-J equations H-J equations A Hamilton-Jacobi equation

More information

概述

概述 OPC Version 1.6 build 0910 KOSRDK Knight OPC Server Rapid Development Toolkits Knight Workgroup, eehoo Technology 2002-9 OPC 1...4 2 API...5 2.1...5 2.2...5 2.2.1 KOS_Init...5 2.2.2 KOS_InitB...5 2.2.3

More information

2015 Chinese FL Written examination

2015 Chinese FL Written examination Victorian Certificate of Education 2015 SUPERVISOR TO ATTACH PROCESSING LABEL HERE Letter STUDENT NUMBER CHINESE FIRST LANGUAGE Written examination Monday 16 November 2015 Reading time: 11.45 am to 12.00

More information

PowerPoint Presentation

PowerPoint Presentation Linear Progamming- the Simple method with greater-than-or-equal-to or equality minimization problem Quantitative deciion making technique /5/6 Tableau form- dealing with greaterthan-or-equal-to contraint

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: [email protected] 177 [P179] (1) - [P181] [P182] (2) - for [P183] (3) - switch [P184] [P187] [P189] [P194] 178 [ ]; : : int var; : int var[3]; var 2293620 var[0] var[1] 2293620

More information

Microsoft Word - 097119012001.htm

Microsoft Word - 097119012001.htm 097 年 度 11901 電 腦 軟 體 設 計 (JAVA) 乙 級 技 術 士 技 能 檢 定 學 科 測 試 試 題 本 試 卷 有 選 擇 題 80 題, 每 題 1.25 分, 皆 為 單 選 選 擇 題, 測 試 時 間 為 100 分 鐘, 請 在 答 案 卡 上 作 答, 答 錯 不 倒 扣 ; 未 作 答 者, 不 予 計 分 准 考 證 號 碼 : 姓 名 : 單 選 題 :

More information

國 立 政 治 大 學 教 育 學 系 2016 新 生 入 學 手 冊 目 錄 表 11 國 立 政 治 大 學 教 育 學 系 博 士 班 資 格 考 試 抵 免 申 請 表... 46 論 文 題 目 申 報 暨 指 導 教 授... 47 表 12 國 立 政 治 大 學 碩 博 士 班 論

國 立 政 治 大 學 教 育 學 系 2016 新 生 入 學 手 冊 目 錄 表 11 國 立 政 治 大 學 教 育 學 系 博 士 班 資 格 考 試 抵 免 申 請 表... 46 論 文 題 目 申 報 暨 指 導 教 授... 47 表 12 國 立 政 治 大 學 碩 博 士 班 論 國 立 政 治 大 學 教 育 學 系 2016 新 生 入 學 手 冊 目 錄 一 教 育 學 系 簡 介... 1 ( 一 ) 成 立 時 間... 1 ( 二 ) 教 育 目 標 與 發 展 方 向... 1 ( 三 ) 授 課 師 資... 2 ( 四 ) 行 政 人 員... 3 ( 五 ) 核 心 能 力 與 課 程 規 劃... 3 ( 六 ) 空 間 環 境... 12 ( 七 )

More information

國立中山大學學位論文典藏.PDF

國立中山大學學位論文典藏.PDF The Study on the New Pension Scheme for Civil Servants Evidence from Kaohsiung County I II 1. III Thesis Abstract Title of Thesis The Study on the New Pension Scheme for Civil Servants: Evidence from Kaohsiung

More information

錄...1 說...2 說 說...5 六 率 POST PAY PREPAY DEPOSIT 更

錄...1 說...2 說 說...5 六 率 POST PAY PREPAY DEPOSIT 更 AX5000 Version 1.0 2006 年 9 錄...1 說...2 說...3...4 說...5 六...6 6.1 率...7 6.2 POST PAY...8 6.3 PREPAY DEPOSIT...9 6.4...10 6.5...11 更...12...12 LCD IC LED Flash 更 兩 RJ11 ( ) DC ON OFF ON 狀 狀 更 OFF 復 狀 說

More information

PowerPoint 演示文稿

PowerPoint 演示文稿 The BitCoin Scripting Language 交易实例 交易结构 "result": { "txid": "921a dd24", "hash": "921a dd24", "version": 1, "size": 226, "locktime": 0, "vin": [ ], "vout": [ ], "blockhash": "0000000000000000002c510d

More information

epub 94-3

epub 94-3 3 A u t o C A D L AY E R L I N E T Y P E O S N A P S T Y L E X R E F - AutoLISP Object ARX A u t o C A D D C L A u t o C A D A u t o d e s k P D B D C L P D B D C L D C L 3.1 Wi n d o w s A u t o C A D

More information

WinMDI 28

WinMDI 28 WinMDI WinMDI 2 Region Gate Marker Quadrant Excel FACScan IBM-PC MO WinMDI WinMDI IBM-PC Dr. Joseph Trotter the Scripps Research Institute WinMDI HP PC WinMDI WinMDI PC MS WORD, PowerPoint, Excel, LOTUS

More information

IBM Rational ClearQuest Client for Eclipse 1/ IBM Rational ClearQuest Client for Ecl

IBM Rational ClearQuest Client for Eclipse   1/ IBM Rational ClearQuest Client for Ecl 1/39 Balaji Krish,, IBM Nam LeIBM 2005 4 15 IBM Rational ClearQuest ClearQuest Eclipse Rational ClearQuest / Eclipse Clien Rational ClearQuest Rational ClearQuest Windows Web Rational ClearQuest Client

More information

QQGQ2.E Power Supplies, Information Technology Equipment Including Ele... 1/10

QQGQ2.E Power Supplies, Information Technology Equipment Including Ele... 1/10 QQGQ2.E232014 - Power Supplies, Information Technology Equipment Including Ele... 1/10 QQGQ2.E232014 Power Supplies, Information Technology Equipment Including Electrical Business Equipment - Component

More information

C/C++ - 数组与指针

C/C++ - 数组与指针 C/C++ Table of contents 1. 2. 3. 4. 5. 6. 7. 8. 1 float candy [ 365]; char code [12]; int states [50]; 2 int array [6] = {1, 2, 4, 6, 8, 10}; 3 // day_mon1.c: # include # define MONTHS 12 int

More information

PowerPoint Presentation

PowerPoint Presentation Decision analysis 量化決策分析方法專論 2011/5/26 1 Problem formulation- states of nature In the decision analysis, decision alternatives are referred to as chance events. The possible outcomes for a chance event

More information

TX-NR3030_BAS_Cs_ indd

TX-NR3030_BAS_Cs_ indd TX-NR3030 http://www.onkyo.com/manual/txnr3030/adv/cs.html Cs 1 2 3 Speaker Cable 2 HDMI OUT HDMI IN HDMI OUT HDMI OUT HDMI OUT HDMI OUT 1 DIGITAL OPTICAL OUT AUDIO OUT TV 3 1 5 4 6 1 2 3 3 2 2 4 3 2 5

More information

C

C C 2017 3 14 1. 2. 3. 4. 2/95 C 1. 3/95 C I 1 // talkback.c: 2 #include 3 #include 4 #define DENSITY 62.4 5 int main(void) 6 { 7 float weight, volume; 8 int size; 9 unsigned long letters;

More information