嘤嘤嘤的LeetCode

2. Add Two Numbers

从地位到高位相加,有进位保留进位。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *p = l1, *q = l2;
        ListNode *res = new ListNode(-1);
        ListNode *pNode = res;
        int carry = 0;
        while(p || q) {
            int val = 0;
            if(p) {
                val += p->val;
                p = p->next;
            }
            if(q) {
                val += q->val;
                q = q->next;
            }
            val += carry;
            if(carry > 0) {
                carry = 0;
            }
            if(val > 9) {
                carry = val / 10; // 下一位进位
                val %= 10;
            }
            ListNode *dig = new ListNode(val);
            pNode->next = dig;
            pNode = pNode->next;
        }
        if(carry != 0) { // 最高位产生的进位
            ListNode *dig = new ListNode(carry);
            pNode->next = dig;
        }
       
        return res->next;
    }
};

3. Longest Substring Without Repeating Characters

题目

用map保存已经见过的字符,遇到新字符时,就从map里找是不是见过。

这个题麻烦在非最长子串中可能包含最长子串的子串,每次找到一个子串后不能直接跳过。

本solution几乎是暴力匹配,性能非常差,待优化。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s == "")     return 0;
        if(s == " ")    return 1;
        int maxLen = 0;
        map<int, int> mapping;
        for(int i = 0; i < s.length(); i++) {
            if(mapping.find(s[i]) != mapping.end()) {
                if(mapping[s[i]] == 0) {
                    mapping.erase(s[i]);    
                } else {
                    mapping.clear();
                }
            }
            mapping[s[i]] = i;
            if(maxLen < mapping.size()) {
                maxLen = mapping.size();
            }
        }
        return maxLen;
    }
};

101. Symmetric Tree

判断是不是镜像二叉树,层次遍历,按层分开,镜像二叉树的每层都是回文串。

注意节点的空孩子用一个填充节点filler,避免只有一个孩子的情况出错。

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        queue<TreeNode*> q;
        q.push(root);
        q.push(NULL);
        vector<int> aLine;
        TreeNode *lastNode = root;
        while(!q.empty()) {
            TreeNode *curr = q.front();
            q.pop();
            if(!curr && !lastNode) {
                break;
            }
            lastNode = curr;
            if(!curr) {
                if(!isPalindrome(aLine)) {
                    return false;
                }
                q.push(NULL);
                aLine.clear();
            } else {
                aLine.push_back(curr->val);
                if(curr->left) {
                    q.push(curr->left);
                } else {
                    if(curr->val != -666) {
                        TreeNode *filler = new TreeNode(-666);
                        q.push(filler);
                    }
                }
                if(curr->right) {
                    q.push(curr->right);
                } else {
                    if(curr->val != -666) {
                        TreeNode *filler = new TreeNode(-666);
                        q.push(filler);
                    }
                }
            }
        }
        return true;
    }
    // 判断是不是回文串
    bool isPalindrome(vector<int> seq) {
        return isPalindrome(seq, 0, seq.size()-1);
    }
    bool isPalindrome(vector<int> seq, int start, int end) {
        if(end-start <= 0)    return true;
        if(seq[start] != seq[end])  return false;
        return isPalindrome(seq, start+1, end-1);
    }
};

109. Convert Sorted List to Binary Search Tree

题目

参考思路:博客

所给序列正好是目标二叉搜索树的中序遍历序列,思路是用中序遍历的思想,自底向上构造二叉树。

C++递归传参记得要对变量引用。

class Solution {
public:
    TreeNode* sortedListToBST(ListNode* head) {
        int listSize = getSize(head);
        return sortedListToBST(head, 0, listSize - 1);
    }
    TreeNode* sortedListToBST(ListNode*& head, int start, int end) { // 注意对head的引用!!
        if(start > end) {
            return NULL;
        }
        int mid = start + (end - start) / 2;
        
        TreeNode *left = sortedListToBST(head, start, mid - 1);
        TreeNode *root = new TreeNode(head->val); //visit root
        root->left = left;
        head = head->next;
        root->right = sortedListToBST(head, mid + 1, end);

        return root;
    }
    int getSize(ListNode *head) {
        ListNode *curr = head;
        int size = 0;
        while(curr) {
            size++;
            curr = curr->next;
        }
        return size;
    }
};

113. Path Sum II

class Solution {
    vector<int> path;
    vector<vector<int> > res;
public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        res = findPath(root);
        vector<vector<int>> total; 
        for(auto aPath : res) {
            int temp = 0;
            for(int val : aPath) {
                temp += val;
            }
            if(temp == sum) {
                total.push_back(aPath);
            }
        }
        return total;
    }
    vector<vector<int>> findPath(TreeNode *root) {
        if(!root) {
            return res;
        }
        path.push_back(root->val);
        bool isLeaf = !(root->left) && !(root->right);
        if(isLeaf) {
            res.push_back(path);
        }
        findPath(root->left);
        findPath(root->right);
        path.pop_back();
        return res;
    }
};

populating-next-right-pointers-in-each-node-ii

题目

1:相比problem i, 本题不是满二叉树了,所以不能逐个左连右,用层次遍历解决。

2:为了把不同层区分开,初始时,在入队root后,入队一个NULL指针,标识一层的结束,以后只要访问到NULL节点,就证明已经访问完当前层,而下一层的所有节点也全部进入了队列,此时在队列末尾再入队一个NULL指针,即可标识下一层的结束。

3:注意处理最后一层的死循环问题。

class Solution {
public:
    void connect(TreeLinkNode *root) {
        queue<TreeLinkNode*> q;
        vector<TreeLinkNode*> lineList;
        q.push(root);
        q.push(NULL);
            
        TreeLinkNode *lastNode = root;
        while(!q.empty()) {
            TreeLinkNode *curr = q.front();
            q.pop();
            if(lastNode == NULL && curr == NULL) { // avoid infinite loop
                break;
            }
            lastNode = curr;
            lineList.push_back(curr);
            if(!curr) { // meets NULL means complete visiting a whole line
                q.push(NULL); // insert NULL indication
            } else {
                if(curr->left) {
                    q.push(curr->left);
                }
                if(curr->right) {
                    q.push(curr->right);
                }
            }
        }
        // connect Node in lineList one by one
        for(int i = 0; i < lineList.size(); i++) {
            if(lineList[i]) {
                lineList[i]->next = lineList[i+1];
            }
        }
        return;
    }
};

populating-next-right-pointers-in-each-node

题目

Clear as the code shows.

class Solution {
public:
    void connect(TreeLinkNode *root) {
        if(!root)    return;
        connect(root->left, root->right);
    }
    void connect(TreeLinkNode *leftRoot, TreeLinkNode *rightRoot) {
        if(!rightRoot)    return;
        
        leftRoot->next = rightRoot;
        
        connect(leftRoot->left, leftRoot->right);
        connect(leftRoot->right, rightRoot->left);
        connect(rightRoot->left, rightRoot->right);
    }
};

triangle

题目

理解题意:由于是个三角形,路径只能从当前数字下一行的左右两个数中间取。

mark自底向上的算法思路:最后一行是原三角形最后一行的值,往上用当前位置的值 + 左下和右下的较小者;这样就保存了最小的部分路径,体现了动态规划的思想;递推式如下:

dp[i][j]] = min(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j]

class Solution {
public:
    int minimumTotal(vector<vector<int> > &triangle) {
        int len = triangle.size();
        if(len == 0 || triangle[0].empty())    return 0;
        if(len == 1)    return triangle[0][0];
        
        for(int i = len-2; i >= 0; i--) {
            for(int j = 0; j < triangle[i].size(); j++) {
                triangle[i][j] = min(triangle[i+1][j], triangle[i+1][j+1]) + triangle[i][j];
            }
        }
        return triangle[0][0];
    }
};

127. Word Ladder

题目

参考思路:Discussion

从start到end构成一条路径,reached集合保存当前已经走到了路径中的哪个点(哪个单词)。

初始start加入到reached中,对于reached集合中的单词(即当前单词)的每一个字母,暴力尝试从a到z的所有替换构成新单词,一旦发现新单词在给定的单词列表中,就把它加入到一个暂时集合toAdd中,并把它从单词列表中移除;即单词列表只保存还未到达的单词,而reached保存已经到达的单词,这是本solution的主要思路。

暴力尝试结束后,toAdd中保存的就是当前能够到达的单词,将其赋给reached,进行下一轮尝试。

class Solution {
public:
    int ladderLength(string start, string end, unordered_set<string> &dict) {
        unordered_set<string> reached;
        reached.insert(start);
        dict.insert(end);
        
        int dis = 1;
        while(reached.find(end) == dict.end()) { // not found
            unordered_set<string> toAdd;
            for(string word : reached) {
                for(int i = 0; i < word.length(); i++){ // try to change every pos in word
                    for(char ch = 'a'; ch <= 'z'; ch++) {
                        string tmp = word; // 不能把原word覆盖了,因为还要尝试改别的位【注】
                        tmp[i] = ch;
                        if(dict.find(tmp) != dict.end()) { // found
                            toAdd.insert(tmp);
                            dict.erase(tmp);
                        }
                    }
                }
            }
            dis++;
            if(toAdd.size() == 0)    return 0;
            reached = toAdd;
        }
        return dis;
    }
};

129. Sum Root to Leaf Numbers

题目

找出二叉树的所有路径,保存下来然后求和。

从根开始访问,先序遍历。

class Solution {
vector<vector<int> > res;
vector<int> path;
public:
    int sumNumbers(TreeNode *root) {
        if(!root)    return 0;
        vector<vector<int> > paths = FindPath(root);
        return CalculateValue(paths);
    }

    vector<vector<int> > FindPath(TreeNode* root) {
        if(!root)
            return res;
        path.push_back(root->val);//visit根
        bool isLeaf=(!root->left) && (!root->right);
        if(isLeaf) {
            res.push_back(path);
        }
        FindPath(root->left);//visit左子树
        FindPath(root->right);//visit右子树
        path.pop_back();
        return res; 
    }

    int CalculateValue(vector<vector<int> > paths) {
        int res = 0;
        for(auto path : paths) {
            stack<int> st;
            for(auto node : path) {
                st.push(node);
            }
            int i = 0;
            while(!st.empty()) {
                res += pow(10, i) * st.top();
                st.pop();
                i++;
            }
        }
        return res;
    }
};

better solution:


class Solution {
public:
    int sumNumbers(TreeNode *root) {
        return sum(root, 0);
    }
    int sum(TreeNode *root, int num) {
        if(!root) {
            return 0;
        }
        bool isLeaf = !(root->left) && !(root->right);
        if(isLeaf) {
            return num * 10 + root->val;
        }
        return sum(root->left, num * 10 + root->val) + sum(root->right, num * 10 + root->val);
    }
};

surrounded-regions

题目

 

palindrome-partitioning

题目

回溯问题,参考思路:Discussion

这题要求找出所有的回文划分,先看任意一个回文划分,即一个正确的解是怎样生成的。

从前往后,逐个字符找,直到找到第一个回文子串,以剩下的子串作为新字符串递归进行寻找;直到新字符串为空,说明得到了一组解。

然后回溯,寻找下一个解。

class Solution {
public:
    vector<vector<string>> partition(string s) {
        vector<vector<string> >res;
        vector<string> part;
        if(s.length()==0)    return res;
        partition1(s, res, part);
        return res;
    }
    void partition1(string s, vector<vector<string> >& res, vector<string>& part) {
        if(s.length()==0) {
            res.push_back(part);
            return;
        }
        for(int i = 1; i <= s.length(); i++) { // 遍历所有划分点
            string subStr = s.substr(0, i);
            if(!isPalindrome(subStr))    continue;
            
            part.push_back(subStr); // choose
            partition1(s.substr(i, s.length()-i), res, part); // explore
            part.pop_back(); // unchoose
        }
    }
    /*
        判断字符串是否是回文
    */
    bool isPalindrome(string str) {
        return isPalindrome(str, 0, str.length()-1);
    }
    bool isPalindrome(string str, int low, int high) {
        int len = high - low;
        if(len <= 0)    return true;
        if(str[low] != str[high])   return false;
        return isPalindrome(str, low+1, high-1);
    }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 

};

clone-graph

克隆图:遍历图 & 保证节点不被重复访问。

图的遍历有深度优先和广度优先两种,本solution采用广度优先遍历:用一个队列保存当前结点的所有邻居节点。

构造一个【旧节点到新节点】的map,每访问到一个节点,在map中增加一条映射关系;如果当前结点已经存在于map中,说明新图中已经有这个节点了,跳过。

class Solution {
public:
    UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) {
        if(!node)    return NULL;
        
        queue<UndirectedGraphNode *> q;
        map<UndirectedGraphNode*, UndirectedGraphNode*> hash;
        UndirectedGraphNode *graph = new UndirectedGraphNode(node->label);
        hash[node] = graph;
        
        q.push(node);
        while(!q.empty()) {
            UndirectedGraphNode *top = q.front();
            q.pop();
            for(auto neighbor : top->neighbors) {
                if(hash.find(neighbor) == hash.end()) { // 不存在key
                    UndirectedGraphNode *newNeighbor = new UndirectedGraphNode(neighbor->label);
                    hash[neighbor] = newNeighbor;
                    q.push(neighbor); // 通过map避免节点重复访问
                }
                hash[top]->neighbors.push_back(hash[neighbor]);
            }
        }
        return graph;
    }
};

single-number-ii

统计每一位1的个数,如果一个数出现了3次,那么这个数的每一位必能被3整除,否则就是出现了一次。

class Solution {
public:
    int singleNumber(int A[], int n) {
        int mark[32];
        for(int i = 0; i < 32; i++) {
            mark[i] = 0;
        }
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < 32; j++) {
                mark[j] += ((A[i] & (1 << j))==0) ? 0 : 1;
            }
        }
        int res = 0;
        for(int i = 0; i < 32; i++) {
            mark[i] = mark[i] % 3;
            if(mark[i] != 0) {
                res += (1 << i);
            }
        }
        return res;
    }
};

single-number

异或去重

copy-list-with-random-pointer

复杂链表的复制,见剑指offer对应题目。

word-break

题目

参考博客

字符串的长度是len,dp数组的长度是len+1,dp[len]初始化为true代表最后一个单词的末尾是可以被划分的。

从后往前找子串,如果子串在dict中,并且子串的下一个位置,dp为true,即新找到的子串后面是一个合法的单词,则置子串起始位置为true,表示又发现一个新单词。

C++ API: string.substr (i, n) // i: 开始字符的位置,n:字符长度

class Solution {
public:
    bool wordBreak(string s, unordered_set<string> &dict) {
        int len = s.length();
        vector<bool> dp(len+1, false); // whether loc can be seperated
        dp[len] = true; // can seperate at the end of a sentence
        for(int i = len-1; i >= 0; i--) {
            for(int j = i; j < len; j++) {
                string sub = s.substr(i, j-i+1);
                if(dict.find(sub) != dict.end() && dp[j+1]) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[0];
    }
};

142. Linked List Cycle II

题目

解这题是一个非常牛逼的思路,仍然是快慢指针的应用。

推荐一篇博客和一个讨论区帖子,讲的非常清楚。

leetcode题解 problem 142 Linked List Cycle II 

Java O(1) space solution with detailed explanation.

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(!head || !(head->next)) return NULL;
        
        // 1.whether it has a circle
        ListNode *slow = head, *fast = head, *entry = head;
        while(fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast) {
                // 2.find entry node of circle
                while(slow != entry) {
                    slow = slow->next;
                    entry = entry->next;
                }
                return entry;
            }
        }
        return NULL;
    }
};

reorder-list

Given a singly linked list LL 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→…

You must do this in-place without altering the nodes' values.

For example,
Given{1,2,3,4}, reorder it to{1,4,2,3}.

本题需求:把链表后半段“倒着”查到前半段即可。

Step1: 快慢指针找到中间节点。

Step1: 反转链表后半段。

Step1: 合并两个链表。

class Solution {
public:
    void reorderList(ListNode *head) {
        if(!head || !head->next)
            return;
        
        // 1. Find middle node
        ListNode *slow = head, *fast = head->next;
        while(fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        
        // 2.Invert right part of Linklist
        ListNode *rightHead = slow->next; //right part
        slow->next = NULL;
        
        ListNode *pre = NULL; //pre init to be NULL, tail of inverted right Linklist is set NULL!
        ListNode *p = rightHead;
        while(p) {
            ListNode *post = p->next;
            p->next = pre;
            pre = p;
            p = post;
        } // p=NULL after loop while pre point to new head of right Linklist
        
        // 3.Insert right part to the left one by one
        p = head; ListNode *q = pre;
        while(p && q) {
            ListNode *post1 = p->next, *post2 = q->next;
            p->next = q;
            q->next = post1;
            p = post1; q = post2;
        }
    }
};

 

binary-tree-preorder-traversal

二叉树先序遍历:先根节点,后左子树,最后右子树。

先序遍历实现起来非常清晰:因为先根后左,所以沿着左节点一直往下,边遍历边访问,即实现了先访问跟后访问左,因为当前根节点是上一个根节点的左孩子;为了访问右子树,每次访问根的时候,把根入栈,依次根据栈顶元素找到右孩子。

先序遍历结束后,遍历指针指向的是空。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode *root) {
        vector<int> res;
        if(!root) {
            return res;
        }
        TreeNode *p = root;
        stack<TreeNode *> s;
        while(p || !s.empty()) {
            while(p){
                res.push_back(p->val);
                s.push(p);
                p = p->left;
            } 
            //**root and left child have been visited
            if(!s.empty()) {
                p = s.top();
                s.pop();
                p = p->right; //**visit right
            }
        }
        return res;
    }
};

binary-tree-postorder-traversal

二叉树后序遍历:先左子树,后右子树,最后根节点。

后序遍历的非递归算法:需要知道上一个被访问的节点是左孩子还是右孩子,如果是左孩子,则需要跳过跟节点,先访问右孩子;如果是右孩子,则应该访问根节点了。

具体到实现:肯定要用到栈,从根开始,左孩子不断进栈,直到左子树最底端节点;然后开始考虑能不能访问根节点,先出栈一个结点(当前根节点),如果根节点没有右孩子或者右孩子已经被访问,则可以访问根节点,否则(上一个被访问的是左孩子)跟节点要重新进栈,以便能够通过跟节点找到右孩子,再右孩子访问完之后,可以继续访问根节点;更新当前结点为“根节点”的右孩子后,就可以进入新一轮,一毛一样的循环。

关于二叉树:二叉树的递归定义决定了,“孩子”和“根”是同一个概念,都是节点,也就是说,每个节点都是根节点,“根”和“孩子”的区别是我们在局部看的时候,用来区分的。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode *root) {
        stack<TreeNode *> s;
        TreeNode *p = root, *lastVisitedNode;
        vector<int> res;
        if(!root)
            return res;
        while(p) {
            s.push(p);
            p = p->left;
        }
        while(!s.empty()) {
            // pop top node,check if it is visitable
            p = s.top();
            s.pop();
            if(!p->right || lastVisitedNode==p->right) { // visit top
                res.push_back(p->val);
                lastVisitedNode = p;
            } 
            else { // push top again, set right child as curr node
                s.push(p);
                p = p->right;
                while(p) {
                    s.push(p);
                    p = p->left;
                }
            }
        }
        return res;
    }
};

insertion-sort-list

Sort a linked list using insertion sort.

复习一下插入排序:默认第一个元素有序,从第二个元素开始,遍历序列,拿当前元素与有序序列里的元素进行比较,其中在有序序列里从后往前查找,把当前元素插入到正确位置,数组的插入是通过移动和交换实现的;遍历结束,排序完成。

这个题要求对链表进行插入排序,难点是无法对有序序列从后往前遍历,只能从前往后,找到比当前节点大的最小节点,把当前结点头插法到这个位置即可。

排序后头节点可能会改变,所以引入dummy节点。

class Solution {
public:
   ListNode *insertionSortList(ListNode *head) {
        ListNode *dummy = new ListNode(-1);
        ListNode *pNode = head;
        while(pNode){
            ListNode *pre = dummy;
            while((pre->next) && (pre->next->val < pNode->val)){
                pre = pre->next;
            }
            ListNode *post = pNode->next; // 记录下一个节点
            pNode->next = pre->next;
            pre->next = pNode;
            pNode = post; 
        }
        return dummy->next;
    }
};

sort-list

Sort a linked list in O(n log n) time using constant space complexity.

链表排序,要求 O(n log n),通用解法归并排序;

复习一下归并排序:先划分,再合并;从序列的中间(mid节点)划分,划分过程递归进行;合并时每次合并两个子序列,设置两个尾指针,分别遍历两个子序列,准备一个临时数组,每轮遍历时拷贝较小者,子序列遍历完后,临时数组中即是合并的结果,然后用临时数组覆盖两个子序列对应位置的值。

难点在于链表没有下标的概念,无法直接找到mid节点,所以采用快慢指针寻找mid节点,参考笔记

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *sortList(ListNode *head) {
        if(!head || !(head->next))
            return head;
        ListNode *mid = findMiddle(head);
        ListNode *right = sortList(mid->next);
        mid->next = NULL; // part left and right
        ListNode *left = sortList(head);
        return Merge(left, right);
    }
    // 快慢指针算出mid和high
    ListNode *findMiddle(ListNode *head){
        ListNode *mid = head, *high = head->next;
        while(high && high->next){ // attenton
            mid = mid->next;
            high = high->next->next;
        }
        return mid;
    }
    // 合并两个独立的链表
    ListNode* Merge(ListNode *left, ListNode *right){
        if(!left)
            return right;
        if(!right)
            return left;
        ListNode *first = left, *second = right;
        ListNode *dummy = new ListNode(0);
        ListNode *pNode = dummy;
        while(first && second){
            if(first->val < second->val){
                pNode->next = first;
                first = first->next;
            } else {
                pNode->next = second;
                second = second->next;
            }
            pNode = pNode->next;
        }
        if(first){
            pNode->next = first;
        }
        if(second){
            pNode->next = second;
        }
        return dummy->next;
    }
};

超时版本 

class Solution {
    struct BNode {
        int val;
        BNode *left;
        BNode *right;
        BNode(int x) : val(x), left(NULL), right(NULL) {}
    };
public:
    ListNode *sortList(ListNode *head) {
        // 建立二叉排序树
        ListNode *pNode = head;
        BNode *root = NULL;
        while(pNode){
            if(!root){
                root = new BNode(pNode->val);
            } else { 
                // 寻找新节点的父节点
                BNode *fNode = root;
                while(fNode){
                    if(fNode->val > pNode->val){
                        fNode = fNode->left;
                    } else {
                        fNode = fNode->right;
                    }
                }
                // 插入新节点
                BNode *insertNode = new BNode(pNode->val);
                if(pNode->val < fNode->val){
                    fNode->left = insertNode;
                } else {
                    fNode->right = insertNode;
                }
            }
            pNode = pNode->next;
        }
        // 中序遍历二叉排序树
        ListNode *newListNode;
        InOrderTraverse(root, newListNode);
        return newListNode;
    }
    void InOrderTraverse(BNode *root, ListNode *newListNode) {
        if(!root){
            return;
        }
        // visit left
        InOrderTraverse(root->left, newListNode);
        // visit root
         ListNode *node = new ListNode(root->val);
        if(!newListNode){
            newListNode = node;
        } else {
            newListNode->next = node;
            newListNode = node;
        }
        // visit right
        InOrderTraverse(root->right, newListNode);
    }
};
  • 买股票系列

121. Best Time to Buy and Sell Stock

一次买入卖出

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size() == 0)  return 0;
        int buy = INT_MAX, profit = 0;
        for(int sell : prices){
            buy = min(buy, sell);
        	profit = max(profit, sell - buy);
        }
        return profit;
    }
};

 

122. Best Time to Buy and Sell Stock II

可以无限次买入卖出

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size() == 0)  return 0;
        int buy = prices[0], profit = 0;
        for(int x : prices){
            if(x > buy){
                profit += x - buy;
            }
            buy = x;

        }
        return profit;
    }
};

123. Best Time to Buy and Sell Stock III

至多买入卖出2次 

很容易想到暴力解法:对价格数组进行划分,在子区间上进行一次买入卖出(见题121),得到各自的最大利润,二者之和的最大值即为所求。如下:

int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len == 0) return 0;
        
        vector<int> l(len, 0), r(len, 0);
        for(int i = 0; i < len; i++) {
            l[i] = maxProfitOneTransaction(prices, 0, i);  // 子区间进行一次买入卖出
            r[i] = maxProfitOneTransaction(prices, i, len);
        }
        int res = 0;
        for(int i = 0; i < len; i++) {
            res = max(res, l[i] + r[i]);
        }
        return res;
    }

注:maxProfitOneTransaction为题121的solution.

继续优化:现在的解法 l 和 r 数组的求解很多步骤是重复的【重叠子问题】

假设得到了 l[i], 对于 l[i+1]:

l[i+1] = max(profit[i+1], l[i])

对于r数组,得到r[i]后很容易得到r[i-1]

r[i-1] = max(profit[i], r[i])

因而需要倒过来遍历。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len == 0) return 0;
        
        vector<int> l(len+1, 0), r(len+1, 0);
        int buy = INT_MAX;
        for(int i = 0; i < len; i++) {
            buy = min(buy, prices[i]);
            l[i+1] = max(prices[i] - buy, l[i]);
        }
        int sell = prices[len-1];
        for(int i = len-2; i > 0; i--){
            r[i] = max(sell - prices[i], r[i+1]);
            sell = max(sell, prices[i]);
        }
        int res = 0;
        for(int i = 0; i < len+1; i++) {
            res = max(res, l[i] + r[i]);
        }
        return res;
    }
};

经验:可以先写出暴力解法,然后分析哪里做了重复计算,将重复计算的值用数组保存下来,就能提升性能;这也是动态规划思想的体现。

参考博客


410. Split Array Largest Sum

这道题要把数组分成m个连续的子数组,m最大为数组长度,此时解为数组中的最大值;m最小为1,此时解为所有元素的和;m的取值只能在1~数组长度之间取,即最终的解只可能是以上两个解之间的某一个数。

采用二分法查找这个数:和的最小值由mid来控制,划分的时候只需考虑子数组的和<=mid的条件下,能划分几个子数组;如果划出来的子数组个数小于给定的个数,说明我们的条件太苛刻,也就是mid太大,这时候二分查找缩小区间在左边区间查找;如果多于给定的个数,说明我们的条件太松,也就是mid太小,这时在右边区间查找。

class Solution {
public:
    // 最大和<=sum的子数组个数
    int largestSum(vector<int>& nums, int sum){
        int count = 0, subSum = 0;
        for(int x : nums){
            subSum += x;
            if(subSum == sum){
                subSum = 0;
                count++;
            }else if(subSum > sum){
                subSum = x;
                count++;
            }

        }
        if(subSum != 0){
            count++;
        }
        
        return count;
    }

    int splitArray(vector<int>& nums, int m) {
        int low = 0, high = 0, mid;
        for(int x : nums){
            if(x > low) low = x;
            high += x;
        }
        // 最终最小的最大和一定出现在low~high之间
        while(low < high){
            mid = low + (high - low) / 2;
            int count = largestSum(nums, mid); //子数组个数
            if(count <= m){ //mid太大
                high = mid;
            }else{
                low = mid + 1;
            }
        }
        return low;
    }
};

416. Partition Equal Subset Sum

引用其他博主的一句话,叫做“典型的背包问题”。就是算背包能不能装得下SUM/2的大小,这里weight和value是等价的,就是整数的值。

但是dp数组怎么构造就是个学问了,这里dp[i] 表示数组中是否有若干个数的和是i,初始化为false,dp[0]是true. 当容量j==x的时候,背包装得下x,dp[0]=true. 随着背包容量的增加,如果j-x等于已经加入背包的物品的重量,则dp[j - x]也是true,表示当前容量放得下x。

dp[capacity]就是问题的解。

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int x : nums){
            sum += x;
        }
        if(sum & 1) return false; // 如果sum是奇数,不可能分成两个等和的子集
        
        int capacity = sum >> 1; //背包容量
        vector<bool> dp(capacity+1, false);
        dp[0] = true;
        for(int x : nums){
            for(int j = capacity; j >= x; j--){
                dp[j] = dp[j] || dp[j - x]; //容量j是否放得下x
            }
        }
        return dp[capacity];
    }
};

456. 132 Pattern

注:s3被初始化为INT_MIN,如果x < s3, 说明s3被更新过,也就是说序列中,在s3的前面存在比s3大的数(找到了s2). 现在又有x < s3,即找到了s1.

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        int s3 = INT_MIN;
        stack<int> stk;  // stk保存s3的候选
        std::reverse(nums.begin(),nums.end());
        for(int x : nums){
            if(x < s3){  //注
                return true;
            }else{  // x > s3
                while(!stk.empty() && x > stk.top()){
                    s3 = stk.top();
                    stk.pop();
                }
                stk.push(x);
            }
        }
        return false;
    }
};

334. Increasing Triplet Subsequence

注:因为c2初始化是INT_MAX,如果出现了x > c2的情况,说明c2被更新过,也即存在一个数比c2小,又有x > c2,即找到一个递增序列。

class Solution {
public:
    /*
    找一个递增序列c1 c2 c3
    */
    bool increasingTriplet(vector<int>& nums) {
        int c1 = INT_MAX, c2 = INT_MAX;
        for(int x : nums){
            if(x <= c1){
                c1 = x;
            }else if(x <= c2){
                c2 = x; // c1 < x <= c2
            }else{ // x > c2 
                return true; //注
            }
        }
        return false;
    }
};

179. Largest Number

自定义排序:两个字符串,如果排在前面比较大,则称第一个字符串 “大于” 第二个。

class Solution {
public:
    static bool cmp(int a, int b){
        string A = to_string(a);
        string B = to_string(b);
        return A + B > B + A;
    }
    string largestNumber(vector<int>& nums) {
        if(!nums.size()) return "";
        std::sort(nums.begin(), nums.end(), cmp);
        string res = "";
        for(auto x : nums){
            res += to_string(x);
        }
        if(res[0] == '0') return "0";
        return res;
    }
    
};

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值