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自底向上的算法思路:最后一行是原三角形最后一行的值,往上用当前位置的值 + 左下和右下的较小者;这样就保存了最小的部分路径,体现了动态规划的思想;递推式如下:
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 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→…
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;
}
};
经验:可以先写出暴力解法,然后分析哪里做了重复计算,将重复计算的值用数组保存下来,就能提升性能;这也是动态规划思想的体现。
参考博客
这道题要把数组分成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];
}
};
注: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;
}
};
自定义排序:两个字符串,如果排在前面比较大,则称第一个字符串 “大于” 第二个。
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;
}
};