赞
踩
思路: 归并排序中归并的思想,对于两个有序队列而言,每次取他们最小值添加到答案队列最大值位置即可排序。
代码:
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { ListNode* result = new ListNode(), *now; // result为返回答案(方便起见初始化为开头为0的一个链表,后续返回他的指向就行), now为每次用于生成新ListNode的变量 ListNode* ed = result; // ed指向链表末尾 while(list1 != nullptr && list2 != nullptr){ // 将两个队列中小的取出 if(list1->val < list2->val) now = new ListNode(list1->val), list1 = list1->next; else now = new ListNode(list2->val), list2 = list2->next; ed->next = now, ed = now; // 将list1,list2的最小值放在队列最大值位置, 更新ed指向 } while(list1 != nullptr){ // 处理list1不为空情况, 直接添加到答案队列就行 now = new ListNode(list1->val), list1 = list1->next; ed->next = now, ed = now; } while(list2 != nullptr){ // 处理list2不为空情况, 直接添加到答案队列就行 now = new ListNode(list2->val), list2 = list2->next; ed->next = now, ed = now; } return result->next; // 因为初始化为一个开头为0的队列, 所以删除开头的0后返回 } };
思路: 对于一个合法的括号序列而言,当前括号序列必然可以拆分成三个部分:与最左侧括号匹配的右括号,这对括号中合法的括号序列,这对括号后合法的序列(例如:‘(()())()’,可以拆分为位置在0和5的括号对 ‘(’ ‘)’,这对括号中的合法括号序列 ‘()()’,这对括号后的合法序列 ‘()’)。那么一个有
n
n
n对括号的括号序列可以看做如下情况{ ‘(’ + 有
l
l
l对括号的合法括号序列 + ‘)’ + 有
n
−
l
−
1
n-l-1
n−l−1对括号的合法括号序列’ }。那么对于当前答案而言,就是对于有
l
l
l对括号的合法括号序列和有
n
−
l
−
1
n-l-1
n−l−1对括号的合法括号序列的组合,由于
l
l
l和
n
−
l
−
1
n-l-1
n−l−1都必然小于
n
n
n,所以当前生成的括号集合可以用以前获得的括号集合生成。
代码:
class Solution { public: vector<string> generateParenthesis(int n) { vector<string> ans[10]; ans[0].push_back(""); // 初始化长度为0的括号序列 ans[1].push_back("()"); // 初始化长度为1的括号序列 for(int i = 2;i <= n;i++){ // 当前求的总括号对数 for(int l = 0;l < i;l++){ // 括号中间合法括号串的括号对数 int r = i - l - 1; // 右合法括号串的括号对数 for(int j = 0;j < ans[l].size();j++){ for(int k = 0;k < ans[r].size();k++){ ans[i].push_back("(" + ans[r][k] + ")" + ans[l][j]); // 将中间和后面的括号集合进行组合获得当前长度的合法串 } } } } return ans[n]; } };
思路: 与21题一样,都是归并排序中归并的思想。每次选所有升序链表中最小值添加到答案的最大位置,不过由于每次都要遍历所有链表,所以这个算法时间复杂度为
O
(
K
∗
N
)
O(K*N)
O(K∗N),
K
K
K为链表个数,
N
N
N为链表中数字总个数。(当然也可以直接遍历一遍所有数字,把他们都扔到一个最小堆里,时间复杂度是
O
(
N
l
o
g
(
N
)
)
O(Nlog(N))
O(Nlog(N)))
代码1(归并):
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* mergeKLists(vector<ListNode*>& lists) { ListNode* result = new ListNode(), *now; // result为返回答案(方便起见初始化为开头为0的一个链表,后续返回他的指向就行), now为每次用于生成新ListNode的变量 ListNode* ed = result; // ed指向链表末尾 int mn; // 指向lists中最小数字最小的串 do{ mn = -1; // 每次初始化为-1, 即不存在 for(int i = 0;i < lists.size();i++){ if(lists[i] != nullptr){ if(mn == -1 || lists[i]->val < lists[mn]->val) mn = i; // 更新mn } } if(mn != -1){ now = new ListNode(lists[mn]->val); ed->next = now, ed = now; // 最小值放在队列最大值位置, 更新ed指向 lists[mn] = lists[mn]->next; } }while(mn != -1); // 如果没找到说明已经取出所有数字了 return result->next; } };
代码2(扔小顶堆):
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* mergeKLists(vector<ListNode*>& lists) { ListNode* result = new ListNode(), *now; ListNode* ed = result; priority_queue<int, vector<int>, greater<int> > qu; while(!qu.empty()) qu.pop(); for(int i = 0;i < lists.size();i++){ // 获得所有数字 while(lists[i] != nullptr) qu.push(lists[i]->val), lists[i] = lists[i]->next; } while(!qu.empty()){ // 输出所有数字 now = new ListNode(qu.top()), qu.pop(); ed->next = now, ed = now; } return result->next; } };
思路: 按题意模拟即可。
代码:
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* swapPairs(ListNode* head) { if(head == nullptr) return head; // 特判初始为空的情况 ListNode* result = new ListNode(0, head); // 初始化答案指针 ListNode* headPre = result, *headAft = head->next; // headPre:head前一个位置的指针, headAft:head后一个位置的指针, 当前要交换的两个数字左边那个为head while(head != nullptr && headAft != nullptr){ head->next = headAft->next; // head后一个指向headAft的后一个 headAft->next = head; // headAft的后一个指向head headPre->next = headAft; // headPre的后一个指向headAft headPre = head; // 更新headPre(右移两位) head = head->next; // 更新head(右移两位) if(head != nullptr) headAft = head->next; // 更新headAft(右移两位) } return result->next; } };
思路: 递归,每K个数据分为一组分别处理。对于每组数据,采用递归的方法,发现如果现在的左指针是右指针的左边一个数,那么将右指针所指的数的next指向左指针。
自己按照思路画个图就清楚了。
代码:
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: // l为当前翻转区间的左指针, r为当前翻转区间的右指针 // 如果当前左指针下一个数字不是右指针所指的数字, 那么递归翻转reverse(l->next, r) // 当执行l->next->next = l之前已经确保l->next之后的所有位置都翻转了, 这时候翻转l->next和l即可 void reverse(ListNode* l, ListNode* r){ if(l->next != r) reverse(l->next, r); l->next->next = l; } ListNode* reverseKGroup(ListNode* head, int k) { // 特判k==1的时候不用处理 if(k == 1) return head; // 用于存储答案 ListNode* result = new ListNode(0, head); // tmp 当前遍历到的位置, nx 下一区间开头, pre 上一区间尾部位置, ttmp 当前区间开头位置 ListNode* tmp = head, *nx, *pre = result, *ttmp; // 当前区间长度 int cnt = 0; while(tmp != nullptr){ cnt = cnt + 1; // 如果区间长为k if(cnt == k){ cnt = 0; nx = tmp->next; // 下一区间开头 ttmp = pre->next; // 当前区间开头 reverse(pre->next, tmp); // 翻转长度为K的区间 ttmp->next = nx; // 更新当前区间末尾的下一位置 pre->next = tmp; // 更新上一区间末尾的下一位置 tmp = ttmp; // 更新当前位置 pre = ttmp; // 更新上一区间末尾位置 } tmp = tmp->next; } return result->next; } };
思路: 用k代表不重复的数字个数,那么k相当于指向下一个存储位置。原数组已经从小到大排序,所以我们只需要判断当前数据是否与前一个相同,如果不同将其添加到答案位置中。
代码:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int k = 1; // 不重复的数字个数
for(int i = 1;i < nums.size();i++){
if(nums[i] != nums[i - 1]) nums[k++] = nums[i];
}
return k;
}
};
思路: 从前往后比较每个数据是否与val相同即可,如果不同添加到答案数组。
代码:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int k = -1; // 记录答案的位置下标
for(int i = 0;i < nums.size();i++){
if(nums[i] != val) nums[++k] = nums[i]; // 如果不同添加到答案中
}
return k + 1;
}
};
思路: 两字符串匹配问题可以用KMP(有关KMP的讲解网上很多)。
代码:
class Solution { public: // 对模式串进行处理, 获取next值 void getNext(int next[], string needle){ next[0] = -1; int i = 0,j = -1; while(i < needle.length()){ if(j == -1 || needle[i] == needle[j]) next[++i] = ++j; else j = next[j]; } } int strStr(string haystack, string needle) { int next[1001]; memset(next, 0, sizeof(next)); getNext(next, needle); int i = 0,j = 0, hLen = haystack.length(), nLen = needle.length(); while(i < hLen && j < nLen){ // 对两串进行匹配 if(j == -1 || haystack[i] == needle[j]) ++i, ++j; // 如果模式串的指针来到了头部, 或者模式串当前字符与字符串的当前字符相同, 那么向右移动两指针 else j = next[j]; // 否则将模式串指针移到上一合适位置 } if(j >= needle.length()) return i - nLen; return -1; } };
思路1: 一眼大数除法,然后又注意到输入的是int类型,如果分解为字符串还是需要用到除法。。。那么可以用二分的方法确定商,然后用快速乘的方法用加法模拟乘法。
(写的最心累的一题)
代码1:
class Solution { public: // 标记是否溢出1为溢出 int overflow = 0; // 用加法方式实现乘法, a*b, 传入的时候a必为正数, 将正数加法转为负数加法, 防溢出 int ksc(int a,int b){ int ans = 0, flag = b < 0 ? -1 : 1; if(!a || !b) return 0; if(b < 0) b = -b; a = -a; while(b){ if(b & 1){ if(flag > 0 && -2147483647 - a > ans){ // 正数溢出 overflow = 1; return 2147483647; }else if(flag < 0 && -2147483648 - a > ans){ // 负数溢出 overflow = 1; return -2147483648; } ans = ans + a; } b >>= 1; if(!b) break; if(flag > 0 && -2147483647 - a > a){ // 正数加法因子溢出 overflow = 1; return 2147483647; }else if(flag < 0 && -2147483648 - a > a){ // 负数加法因子溢出 overflow = 1; return -2147483648; } a += a; } if(flag < 0) return ans; if(ans == -2147483648){ overflow = 1; return 2147483647; }return -ans; } // 判断某个数是否为2的幂次 bool isPow(int x){ if(x < 0) x = -x; int times = 0; while(x){ if(x & 1) ++times; x >>= 1; } if(times > 1) return false; return true; } int divide(int dividend, int divisor) { // 二分左指针, 右指针, 中间值, 当前计算值, 答案 int l = -1073741824,r = 1073741823,mid,tmp, ans = 0; if(divisor == 1) return dividend; // 除数为1直接输出 if(dividend == 0) return 0; // 被除数为0直接输出0 if(dividend == divisor) return 1; // 两者相等直接输出1 if(divisor == -2147483648) return 0; // 除数为-2147483648值必为0(被除数为-2147483648的情况上面考虑过了) if(divisor == -1){ // 输出除数为-1的情况 if(dividend == -2147483648) return 2147483647; // 正数溢出 else return -dividend; } int add = 0; // 最后加的数 // 如果被除数为-2147483648则直接把他+1,如果除数是2的幂次, 那么最后要加一个数 // 因为-2147483648除以2的幂次的数, 最后答案绝对值比-2147483647除以2的幂次的数绝对值大1 if(dividend == -2147483648 && isPow(divisor)){ if(divisor > 0) add = -1; else add = 1; } if(dividend == -2147483648) dividend++; // 把除数化为正数 if (divisor < 0) dividend = -dividend, divisor = -divisor; // 二分 while(l <= r){ mid = (l + r) >> 1; overflow = 0; // 初始化overflow标记 tmp = ksc(divisor, mid); // 计算divisor*mid // 分类讨论, 这部分不细说了, 按算法画个图就懂了, 需要注意的是min和max不能乱用 if(dividend > 0 && tmp <= 0) ans = max(ans, mid), l = mid + 1; else if(dividend > 0){ if(tmp < dividend) ans = mid, l = mid + 1; else if(tmp == dividend && !overflow) ans = mid, r = mid - 1; else r = mid - 1; }else if(dividend < 0 && tmp >= 0) ans == min(ans, tmp), r = mid - 1; else{ if(tmp < dividend) l = mid + 1; else if(tmp == dividend) ans = mid, l = mid + 1; else if(!overflow) ans = min(ans, mid), r = mid - 1; else r = mid - 1; } } return ans + add; } };
思路2: 写太心累了,换个思路,除法也可以用位运算进行模拟。
位运算模拟除法思路:
先把被除数和除数都化为二进制。对于长度相同的被除数和除数而言,如果被除数此时值大于除数,那么说明 被除数 / 除数 此时商为1(不可能大于1,因为在二进制条件下,商大于等于2的条件是 被除数>=(除数<<1))。那我们思考当前被除数左移k位的情况,被除数除以除数的商为 1<<k。那我们只需要将被除数从高位到低位,与除数进行比较,假如被除数大于等于除数,那么向答案添加 1<<此时左移位数,并且从被除数中减去 除数<<此时左移位数。
代码2:
class Solution { public: bool isPow(int x){ if(x < 0) x = -x; int times = 0; while(x){ if(x & 1) ++times; x >>= 1; } if(times > 1) return false; return true; } int divide(int dividend,int divisor){ // 前面特判和代码1一样, 就不再说了, 看代码1就行 if(divisor == 1) return dividend; if(dividend == 0) return 0; if(dividend == divisor) return 1; if(divisor == -2147483648) return 0; if(divisor == -1){ if(dividend == -2147483648) return 2147483647; else return -dividend; } // flag用于判断最终答案正负, 其余与上一代码一样 int add = 0, flag = 1, ans = 0, tmp; if(dividend == -2147483648 && isPow(divisor)){ if(divisor > 0) add = -1; else add = 1; } if(dividend == -2147483648) dividend++; if (divisor < 0) dividend = -dividend, divisor = -divisor; if(dividend < 0) flag = -1, dividend = -dividend; // 位运算模拟除法 for(int i = 31;i >= 0;i--){ // 将被除数右移i位, 当他大于等于除数的时候, 说明这时候 (1<<i) 是 (被除数>>i)<<i / 除数 的商 // 那我们在被除数中减去这一值然后继续遍历 tmp = (dividend >> i); if(tmp >= divisor){ ans += (1 << i); dividend -= divisor << i; } } return ans * flag + add; } };
思路: 哈希+滑动窗口的思想。因为所有字符串都是等长的,所以我们可以将字符串看为
w
o
r
d
s
[
0
]
.
l
e
n
g
t
h
(
)
words[0].length()
words[0].length()种形式,将字符串开头删除
0
−
w
o
r
d
s
[
0
]
.
l
e
n
g
t
h
(
)
0 - words[0].length()
0−words[0].length()个字符串形成每种形式。对于每种形式而言,我们从开头开始将每
w
o
r
d
s
[
0
]
.
l
e
n
g
t
h
(
)
words[0].length()
words[0].length()个字母看成一个个独立的字符串,对其进行哈希。然后开始建立滑动窗口,保证每次窗口内值合法。
保持合法性操作:
class Solution { public: vector<int> findSubstring(string s, vector<string>& words) { // 存储答案 vector<int> ans; // 记录集合中每个字符串出现的个数 map<int,int> word; ans.clear(), word.clear(); if(words.size() == 0 || words[0].length() == 0) return ans; // HS:哈希因子 mod:模数 has[i]:HS^i pre[i]:从开头到字符串i-1位的哈希值 long long HS = 13331, mod = 1000000007, has[10001] = {1}, pre[10001] = {0}, tmp, ttmp; // 计算每一个字符串的哈希值 for(int i = 0;i < words.size();i++){ tmp = 0; for(char j : words[i]) tmp = (tmp * HS + j) % mod; word[tmp] += 1; } // 计算字符串s到的哈希值 for(int i = 0;i < s.length();i++) pre[i + 1] = (pre[i] * HS + s[i]) % mod, has[i + 1] = (has[i] * HS) % mod; // 初始下标, 或者说字符串s删除了头i个字母 for(int i = 0;i < words[0].length();i++){ map<int,int> mp; // 记录窗口内每个字符串出现的次数 mp.clear(); // l:左窗口所指字符串末尾位置+1, times:窗口内满足条件的字符串个数(直接用右窗口与左窗口计算也行,但我懒得写) int l = i + words[0].length(),times = 0; // j为右窗口位置, 所对应的是右窗口所对应字符串末尾字符位置+1 for(int j = i + words[0].length();j <= s.length();j += words[0].length()){ // 计算右窗口所指字符串哈希值 tmp = (pre[j] - pre[j - words[0].length()] * has[words[0].length()] % mod + mod) % mod; // 如果不在集合里, 那就清空窗口, 左窗口移到当前右窗口右侧 if(word.find(tmp) == word.end()) mp.clear(), l = j + words[0].length(), times = 0; // 当前字符串出现次数少于规定次数, 加入统计 else if(mp[tmp] < word[tmp]) mp[tmp] += 1, times++; // 当前字符串出现次数等于规定次数, 将窗口内当前字符串个数减一,再加入统计 else{ mp[tmp] += 1; while(true){ // 当前左窗口所指字符串哈希值 ttmp = (pre[l] - pre[l - words[0].length()] * has[words[0].length()] % mod + mod) % mod; // 移除字符串统计, 右移左窗口 mp[ttmp] -= 1; l += words[0].length(); // 直到右窗口要添加的字符串在窗口内出现次数为集合中出现次数-1 if(ttmp == tmp) break; times--; } } // 如果当前窗口内字符串满足条件, 添加到答案中 if(times == (int)words.size()) ans.push_back(l - words[0].length()); } } return ans; } };
思路: 从后往前遍历,找到第一个比后面数列最大值小的数字,将这个数字与比他大数字的最小值交换,然后把这个数字位置之后的所有数字从小到大排序。注意特别处理一下不存在下一个排列的情况(从头到尾sort)。
代码:
class Solution { public: void nextPermutation(vector<int>& nums) { int mx = nums.size() - 1,flag = 0,tmp; // mx最大值位置, flag是否找到下一个排列, tmp临时变量 for(int i = nums.size() - 2;i >= 0;i--){ // 找到第一个比后面最大值小的位置 if(nums[i] < nums[mx]){ // 如果找到了 for(int j = i + 1;j < nums.size();j++){ // 找到这个位置后面比这个位置的值大的最小值 if(nums[j] > nums[i] && nums[j] < nums[mx]) mx = j; } swap(nums[mx], nums[i]); // 交换 flag = 1; sort(nums.begin() + i + 1, nums.end()); // 把这个位置后面的数字升序排序 break; }else mx = i; } if(!flag) sort(nums.begin(), nums.end()); // 当前是最大排列则直接从头到尾sort return; } };
思路: dp的思想。对于每一个符合条件的括号序列,最后一位必然是 ‘)’ ,所以我们考虑对于右括号而言,括号串成立的情况。
class Solution { public: int longestValidParentheses(string s) { // dp[i]记录以s[i-1]为结尾的合法括号串最长长度 int dp[30004], ans = 0; memset(dp, 0, sizeof(dp)); for(int i = 1;i < s.length();i++){ // 如果')'前一个是'(', 那么到当前为止, 最长合法括号串长度为2+'('前最长合法括号串长度 if(s[i] == ')' && s[i - 1] == '(') dp[i + 1] = dp[i - 1] + 2; // 如果')'前是多个')', 并且以')'前这些')'为末尾的字符串存在一个最长合法字符串, 并且在这个合法字符串前是一个'('与末尾')'对应 // 那么到当前为止, 最长合法括号串长度为2+末尾前最长合法字符串长度+'('前最长合法字符串长度 else if(s[i] == ')' && s[i - 1] == ')' && i - dp[i] - 1 >= 0 && s[i - dp[i] - 1] == '(') dp[i + 1] = dp[i] + 2 + dp[i - dp[i] - 1]; ans = max(ans, dp[i + 1]); } return ans; } };
思路: 前半段是升序的,后半段也是升序的,并且后一段每个数字小于前一段第一个数字,那就是一个二分的思想。
代码:(其实可以写短点,为了好理解就没压行)
class Solution { public: int search(vector<int>& nums, int target) { int l = 0, r = nums.size() - 1, mid; while(l <= r){ mid = (l + r) >> 1; // 答案就输出 if(nums[mid] == target) return mid; // 分类讨论, 如果答案是在左边的 if(target >= nums[0]){ if(nums[mid] >= nums[0]){ // 在左边的单调递增数组上二分 if(nums[mid] < target) l = mid + 1; else r = mid - 1; }else r = mid - 1; // 答案在左边, 所以当下标在右边肯定不成立 }else{ // 如果答案在右边 if(nums[mid] < nums[0]){ // 在右边的单调递增数组上二分 if(nums[mid] < target) l = mid + 1; else r = mid - 1; }else l = mid + 1; // 答案在右边, 所以当下标在左边肯定不成立 } } return -1; } };
思路: 二分就完事了。先二分左侧端点,再二分右侧端点。
代码:
class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { int l = 0, r = nums.size() - 1,mid,ansl = nums.size(),ansr = -1; // ansl 为左端点位置, ansr 为右端点位置 while(l <= r){ mid = (l + r) >> 1; if(nums[mid] < target) l = mid + 1; else ansl = min(ansl, mid), r = mid - 1; } // 从左端点开始也许能略微快一点( l = ansl, r = nums.size() - 1; while(l <= r){ mid = (l + r) >> 1; if(nums[mid] <= target) ansr = max(ansr, mid), l = mid + 1; else r = mid - 1; } vector<int> vt; vt.clear(); if(ansr < ansl) vt.push_back(-1), vt.push_back(-1); else vt.push_back(ansl), vt.push_back(ansr); return vt; } };
思路: 二分第一个大于等于他的位置。
代码:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while(l <= r){
if(nums[(l + r) >> 1] < target) l = l + 1;
else r = r - 1;
}
return l;
}
};
思路: 一个有效的数独(部分已被填充)不一定是可解的。所以只要符合前三条规律即可。注意一个问题是数组定义在函数内部要初始化。
每次将当前格子的数字添加到所在行中、列中、9*9矩阵中,更新这个部分的数字个数,如果个数大于1,那么说明有重复,输出-1
代码:
class Solution { public: bool isValidSudoku(vector<vector<char>>& board) { // row[i][j]记录第i行数字j+1的数量 // col[i][j]记录第i列数字j+1的数量 // grid[i][j][k]记录第i行第j个9*9矩阵中数字k+1的数量 int row[9][9], col[9][9], grid[3][3][9]; memset(row, 0, sizeof(row)); memset(col, 0, sizeof(col)); memset(grid, 0, sizeof(grid)); for(int i = 0;i < 9;i++){ for(int j = 0;j < 9;j++){ if(board[i][j] == '.') continue; if(++row[i][board[i][j] - '1'] > 1) return false; if(++col[j][board[i][j] - '1'] > 1) return false; if(++grid[i/3][j/3][board[i][j] - '1'] > 1) return false; } } return true; } };
思路: 题目没有说如果不成立怎么输出,那就默认必然成立。直接暴力递归,对于每个空白位置而言,最多有九种情况,我们对于每种情况判断下是否合理,如果合理检测下一个空白位置的情况。如果有个位置1-9都不合法,那么回溯到上一状态,让上一状态换一个值。
代码:
class Solution { public: // row[i]第i行有哪些数字, 用二进制表示, 如果row[i] & (1 << k) > 0, 则说明row[i]行中有数字k-1 // col[i]第i列有哪些数字, 用二进制表示 // grid[i][j]表示从左上角开始, 每个方阵有哪些数字 // ans记录每个位置答案 // yi[i]记录1<<i的值 // cnt为空着的位置个数 // emp数组记录空白位置的坐标 int row[9], col[9], grid[3][3], ans[9][9], tmp, yi[10], cnt = 0; pair<int,int> emp[90]; int dfs(int x){ // 如果所有空白位置都成立了, 那返回真 if(x > cnt) return 1; int lx = emp[x].first, ly = emp[x].second; for(int i = 0;i < 9;i++){ // 判断行/列/方阵是否有当前数 if(row[lx] & yi[i]) continue; if(col[ly] & yi[i]) continue; if(grid[lx / 3][ly / 3] & yi[i]) continue; // 更新当前位置答案,更新行/列/方阵包含的数字 ans[lx][ly] = i + 1; row[lx] |= yi[i]; col[ly] |= yi[i]; grid[lx / 3][ly / 3] |= yi[i]; // 检查当前情况下之后的空白位置成立情况 if(dfs(x + 1)) return 1; // 不成立的时候还原行/列/方阵/答案数组 ans[lx][ly] = 0; row[lx] ^= yi[i]; col[ly] ^= yi[i]; grid[lx / 3][ly / 3] ^= yi[i]; } return 0; } void solveSudoku(vector<vector<char>>& board) { // 计算1 << i的值, 方便后面计算 for(int i = 0;i < 10;i++) yi[i] = 1 << i; // 统计初始情况 for(int i = 0;i < 9;i++){ for(int j = 0;j < 9;j++){ if(board[i][j] == '.') emp[++cnt] = {i, j}; else{ tmp = board[i][j] - '1'; ans[i][j] = board[i][j] - '0'; row[i] |= yi[tmp]; col[j] |= yi[tmp]; grid[i / 3][j / 3] |= yi[tmp]; } } } dfs(1); // 更新答案数组 for(int i = 0;i < 9;i++){ for(int j = 0;j < 9;j++) board[i][j] = (char)(ans[i][j] + '0'); } } };
思路: 暴力,从头到尾按规则寻找相同字符串将其添加到当前答案就行。
代码:
class Solution { public: // x个字符y转为相应字符串 string intToString(int x,char y){ string s = " "; s[0] = y; while(x){ s = " " + s; s[0] = (char)('0' + x % 10); x /= 10; } return s; } string countAndSay(int n) { string s = "1", ans = "1"; for(int i = 2;i <= n;i++){ int l = 0, r = 0; ans = ""; while(l < s.length()){ while(r < s.length() && s[l] == s[r]) ++r; ans = ans + intToString(r - l, s[l]); l = r; } s = ans; } return ans; } };
思路: 一眼dp。如果是要计算答案等于target的集合个数,那就只要简单dp即可,本题还有个需要输出集合的麻烦事。
对于每个原数列candidates中的数字而言,有两种使用方法:1.单独成集合,即k个当前数字形成的集合;2.添加到已有的集合之后。
代码:
class Solution { public: vector<vector<int>> combinationSum(vector<int>& candidates, int target) { vector<vector<int>> dp[45]; // dp[i]记录和为i的所有集合 vector<int> tmp, ttmp; dp[0].push_back(tmp); // dp[0]只有一个空集合 for(int & candidate : candidates){ // 遍历原数组每个值 // **从大到小** 一定要从大到小, 不然前面状态的改变会影响后面 for(int i = target - candidate; i >= 0;i--){ if(dp[i].empty()) continue; tmp.clear(); // 向当前集合后面添加j/candidate个candidate的情况 for(int j = candidate;j + i <= target;j += candidate){ tmp.push_back(candidate); for(auto & k : dp[i]){ ttmp = k; ttmp.insert(ttmp.end(), tmp.begin(), tmp.end()); dp[j + i].push_back(ttmp); } } } } return dp[target]; } };
思路: 与上一题一样,区别在于不能每个数字使用无限次。但是原数组中可能有相同的数字,那么就相当于对于数组中每个不相同的数字,每个数字可以使用有限个,那么加上个数限制即可。
代码:
class Solution { public: vector<vector<int>> combinationSum2(vector<int>& candidates, int target) { std::sort(candidates.begin(), candidates.end()); // 对于数组排序 vector<int> tmp, ttmp; vector<vector<int>> dp[45]; dp[0].push_back(tmp); int l = 0, r = 0; while(l < candidates.size()){ // 找到当前数字个数 while(r < candidates.size() && candidates[r] == candidates[l]) ++r; for(int i = target - candidates[l];i >= 0;i--){ if(dp[i].empty()) continue; tmp.clear(); // 多加个数字个数限制 for(int j = candidates[l];j + i <= target && j / candidates[l] <= (r - l);j += candidates[l]){ tmp.push_back(candidates[l]); for(auto & k : dp[i]){ ttmp = k; ttmp.insert(ttmp.end(), tmp.begin(), tmp.end()); dp[j + i].push_back(ttmp); } } } l = r; } return dp[target]; } };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。