整理进度:1-201,518-581,1017-1024
待归档:
记忆化广搜:
#576 Out of Boundary Paths
排序问题:
#581 Shortest Unsorted Continuous Subarray
凸包:
#587 Erect the Fence
哈希,重复模式:
#594 Longest Harmonious Subsequence
滑动窗口:
#611 Valid Triangle Number
找规律:
#621 Task Scheduler
动规/记忆化搜索/贪心:
#630 Course Schedule III
#3 Longest Substring Without Repeating Characters
i = max(i, lastpos[s[j]])
,效率更高一些#11 Container With Most Water
前指针、后指针中间的区域为一个桶,桶的面积为高乘以宽,当i、j为两端时,桶宽最大,当i、j向中间滑动时,只有桶高增加,桶面积才有可能增大。若移动长板,桶高不可能增大,因此只能移动短板对应的指针。
#1 2Sum
当前后指针和大于k,前移后指针(和变小),否则后移前指针(和变大)
#15 3Sum
遍历并固定第一个数,从而把问题转换为2Sum
同理 #16 3Sum Closest、#18 4Sum
#76 Minimum Window Substring
滑动窗口前后指针都从起始位置开始,先移动后指针使得窗口内数组符合要求,然后移动前指针直到窗口内数组不再符合要求,随后再次移动后指针,依此类推。
#4 Median of Two Sorted Arrays
i + j = m - i + n - j
A[i - 1] <= B[j] && B[j - 1] <= A[i]
#33 Search in Rotated Sorted Array
首先用二分搜索找到数组中的最小元素,之后就可以构造出旋转前的数组元素对应在旋转后的数组中的位置索引,根据这一索引进行全局的二分搜索即可。#153 Find Minimum in Rotated Sorted Array 和这道题是一样的
若考虑数组中存在重复元素,则需要在查找数组最小元素的二分搜索步骤中添加一个预处理步骤,保证二分区间的左右端点值不同,见 #81 Search in Rotated Sorted Array II、#154 Find Minimum in Rotated Sorted Array II
#50 Pow(x, n)
二分求快速幂,注意考虑幂的正负
#69 Sqrt(x)
二分确定x的范围,比较x^2与a的大小
#74 Search a 2D Matrix
其实不能算是分治策略,但形式有点像二维的二分搜索,所以干脆放在这里
从右上角开始进行搜索,若小于目标值,则向下搜索,若大于目标值,则向左搜索,时间复杂度O(m + n)
#162 Find Peak Element
很像之前遇到过的一个面试题目
这道题如果用O(n)的算法是非常简单的,我们需要找到一个O(logn)的算法
二分策略的一般流程是,先找到区间的中点元素,将这个元素与某一个标准进行对比,从而决定接下来的区间选择哪一侧
对比标准常见的有:1、与某个目标值作对比,比如二分查找;2、与左右端点值作对比,比如上面的 #33 Search in Rotated Sorted Array;3、与中点元素两侧的元素作对比,相当于判断中点元素处的单调性,比如这道题就是。
我们判断nums[mid]
与左右两侧元素的大小,如果这一点正好是peak,就直接输出,如果某一侧单调递增,则在这一侧必存在一个peak元素(若递增后开始递减,则存在peak,若一直递增到数组末尾,因为设定nums[nums.size()] = -∞
,所以此时末尾元素是一个peak),进而可以将搜索区间决定到这一侧
#5 Longest Palindromic Substring
dp[i][j]
表示子串 [i, j] 是否为回文子串#10 Regular Expression Matching
算是经典题目,.
表示任意匹配单字符,*
表示前一字符可以出现任意次数
dp[i][j]
表示模式串前 i 位是否能与字符串前 j 位匹配
#32 Longest Valid Parentheses
dp[i]
表示以i为结尾的最长合法括号子串长度
当s[i] == '('
时,dp[i] = 0
当s[i] == ')'
时,
①若s[i - 1] == '('
,则dp[i] = dp[i - 2] + 2
②若s[i - 1] == ')' && s[i - dp[i - 1] - 1] == '('
,则dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2]
#44 Wildcard Matching
和#10类似的字符串匹配问题,这里*
不再代表前一字符出现任意次数,而是代表任意序列(可为空)
dp[i][j]
表示字符串前前 i 位与模式串前 j 位匹配dp[i][j] = dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '?'), if p[j - 1] != '*'
dp[i][j] = dp[i][j - 1] || dp[i - 1][j], if p[j - 1] == '*'
*
,则记录当前模式串/字符串遍历位置pi与si,继续往下匹配,若匹配失败,则返回记录的位置,字符串每次匹配失败后重新开始匹配的位置需要前移一位#53 Maximum Subarray
用dp[i]
表示以i为结尾的子数组最大值,则dp[i] = max(dp[i - 1] + arr[i], arr[i])
,由于dp[i]
仅与dp[i - 1]
相关,所以可以降维到0维。
同理**#121 Best Time to Buy and Sell Stock**
#188 Best Time to Buy and Sell Stock IV
同系列的还有 #121 Best Time to Buy and Sell Stock、#122 Best Time to Buy and Sell Stock II、#123 Best Time to Buy and Sell Stock III
第k次购入仅和第k-1次卖出时的收益相关,第k次卖出仅和第k次购入时的收益相关
以rele[i][j]
表示 i 时刻进行第 j 次卖出的最大收益,以hold[i][j]
表示 i 时刻进行第 j 次买入的最大收益
for(int i=0; i<prices.size(); i++){
for(int j=k; j>=1; j--){
rele[j] = max(rele[j], prices[i]-hold[j]);
hold[j] = min(hold[j], prices[i]-rele[j-1]);
maxProfit=max(maxProfit, rele[j]);
}
}
#198 House Robber
同系列的还有 #213 House Robber II、#337 House Robber III
个人感觉和前面的Best Time to Buy and Sell Stock是同一类型的题目,都是在每个时间步有两种不同的状态,区别在于Stock题目两种状态之间必须相互转移,而Robber这道题状态转移不一定发生在两种状态之间。
这类题目可以推广到多个不同状态。
#72 Edit Distance
类似正则匹配的方程,dp[i][j]
表示a串0…i - 1位与b串0…j - 1位的编辑距离
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1[i - 1] == word2[j - 1])
dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(dp[i - 1][j - 1] + 1, min(dp[i][j - 1] + 1, dp[i - 1][j] + 1));
}
}
#518 Coin Change 2
以dp[i]
表示 i 有多少种组合方法,这里需要注意,因为找零是一个组合问题,要忽略顺序,所以选用类似带数量限制的背包问题的方法求解,amount放在内层循环,并倒序遍历从而防止重复选用同一种面额。
for (int j = 0; j < coins.size(); ++j) {
for (int i = amount; i >= 1; --i) {
for (int k = i / coins[j]; k >= 1; --k) {
dp[i] += dp[i - coins[j] * k];
}
}
}
#96 Unique Binary Search Trees
树形动规,遍历所有根节点的位置,那么以这一点为根的不同BST数量为其不同左子树的数量乘以不同右子树的数量。
#97 Interleaving String
dp[i][j]
表示a数组的前 i 位与b数组的前 j 位是否能够交错构成c数组的前 i + j 位
dp[i][j] = dp[i - 1][j] && a[i - 1] == c[i + j - 1] || dp[i][j - 1] && b[j] == c[i + j - 1]
#115 Distinct Subsequences
dp[i][j]
表示模式串0…i - 1与字符串0…j - 1位有多少distinct subsequences
dp[i][j] = dp[i][j - 1] + (t[i - 1] == s[j - 1] ? dp[i - 1][j - 1] : 0)
#132 Palindrome Partitioning II
很容易能想到一个O(n^3)的动规,用dp[i][j]
表示i…j段的最小切数,那么dp[i][j] = min(dp[i][k] + dp[k][j] + 1)
,相当于在k处切一刀时,i…j的最小切数,但这种方法会超时。
一个更好的办法是用一个O(n^2)的动规,以dp[i]
表示i…n - 1段的最小切数,那么若i…j段为回文串时,则dp[i] = max(dp[i], dp[j + 1] + 1)
,相当于在j后面切一刀
#152 Maximum Product Subarray
因为存在负数,所以需要同时考虑最大乘积和最小乘积
dp_max[i]
表示以第 i 个数字结尾的子数组的最大乘积
dp_min[i]
表示以第 i 个数字结尾的子数组的最小乘积
遍历一遍数组,当第 i 个数字大于0时
dp_max[i] = max(nums[i], nums[i] * dp_max[i - 1])
dp_min[i] = min(nums[i], nums[i] * dp_min[i - 1])
当第 i 个数字小于0时
dp_max[i] = max(nums[i], nums[i] * dp_min[i - 1])
dp_min[i] = min(nums[i], nums[i] * dp_max[i - 1])
因为动规只与上一个状态相关,所以可以用滚动数组降维到0维,空间复杂度O(1),时间复杂度O(n)
#174 Dungeon Game
需要计算的是到达终点时所需要的最小初始生命值
这类题目应该倒推,从终点开始往回计算从某个位置开始走到终点所需要的到达这一点最小初始生命值
用dp[i][j]
表示到达(i, j)坐标前最小需要多少生命值,从而能够支撑骑士从这一点开始走到终点
vector<vector<int>> f(m + 1, vector<int> (n + 1, INT_MAX));
f[m - 1][n] = 1, f[m][n - 1] = 1;
for (int i = m - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
int need = min(f[i][j + 1], f[i + 1][j]) - dungeon[i][j];
f[i][j] = need <= 0 ? 1 : need;
}
}
return f[0][0];
#542 01 Matrix
二维矩阵动规,也可以用广搜做
有四个方向需要更新状态,单向更新会缺失状态,所以需要做两遍
从左上角到右下角做一遍,再从右下角到左上角做一遍
感觉和 #135 Candy 是一类
#546 Remove Boxes
一个数组里,去除中间连续的一段得到一个分数,去除后两端接起来产生一个新的状态
这一类题没有办法用二维动规表示全部的状态,因此增加一维
在这道题的设定下,增加第三维表示在[i, j]段左方有多少个与i相同的连续元素
class Solution {
public:
int removeBoxes(vector<int>& boxes)
{
int dp[100][100][100] = {0}; // number of boxes would not exceed 100
int n = boxes.size();
return operate(boxes, 0, n-1, 0, dp); // in the closed interval [0, n-1]
}
int operate(vector<int>& boxes, int i, int j, int k, int dp[100][100][100])
{
// [i, j] is the operating closed interval
// k is the number of adjacent boxes on the left of boxes[i] with the same value
if (i>j) return 0;
if (dp[i][j][k]>0) return dp[i][j][k];
// start with boxes[i] and interval [i+1, j]
int res = (k+1)*(k+1) + operate(boxes, i+1, j, 0, dp);
for (int m=i+1; m<=j; m++)
{
// if boxes[i]==boxes[m], we can thus eliminate interval [i+1, m-1]
// to make boxes[i] and boxes[m] adjacent to have a higher score
if (boxes[i]==boxes[m])
// if boxes[i] and boxes[m] are adjacent, then
// there will be k+1 boxes on the left of boxes[m] with the same value
res = max( res, operate(boxes, i+1, m-1, 0, dp)+operate(boxes, m, j, k+1, dp) );
}
dp[i][j][k] = res;
return res;
}
};
#552 Student Attendance Record II
多状态之间的互相转换,找到状态转移方程即可,不难,但很典型
#553 Optimal Division
记忆化搜索,重点是搞明白怎么搜
除法本质上可以分成两侧,左边除以右边,而其最大值发生在左侧最大右侧最小的时候,因此我们需要做的就是得到每个区间改变计算优先级之后的最大以及最小值
跟 #152 Maximum Product Subarray 本质上是一个类型,要善于发现题目的本质
#629 K Inverse Pairs Array
求1-n这些数字能排列成的含有k个逆序对的序列的数量,记为dp[n][k]
,那么当增加一个新的数字n的时候,若要组成k个逆序对,考虑将n放在哪个位置,若放在第n位,则不会引入新的逆序对,这时有dp[n - 1][k]
种序列,若放在第i位,则会引入n - i个逆序对,若还想组成k个逆序对,有dp[n - 1][k - (n - i)]
种序列。
总的来说,状态转移方程为dp[n][k] = dp[n - 1][k] + d[n - 1][k - 1] + ... + dp[n - 1][max(0, k - (n - 1))]
#630 Course Schedule III
可以证明,如果一个上课序列成立,那么如果将这个序列按照课程结束时间进行重排列,这个序列依然成立
因为如果st + dur_b < st + dur_b + dur_a <= ed_a <= ed_b,那么一定有st + dur_a < st + dur_a + dur_b <= ed_a <= ed_b,因此,我们只用考虑在课程结束时间这一维度上有序的上课序列,也就相当于只考虑每节课上或者不上,搜索时间复杂度从全排列的n!降为2^n,同时,这种形式的搜索可以转化为二维DP,以dp[st][k]
表示从st时刻开始上课,从第k节课开始选,能够完成的最多课程数
这个方法时间复杂度上还是略高的,进一步分析,可以把这道题转化为一个贪心问题——尽可能选择每一节课,如果出现了选择了第i节课后会超出其结束时间的情况,就从前i节课中已选的课程中放弃一门持续时间最长的课,这样一来,就可以保证选择的课程数量没有减少,但最优化了上完已选课程所需要的总时间
#1019 Next Greater Node In Linked List
维护一个栈,保持栈里的元素是还没有找到next greater node的元素,那么遍历时见到的第一个大于他的元素就一定是next greater node。
遇到新元素的时候,让栈顶所有比新元素小的node出栈,并把他们的next greater node标记为新元素,最后将新元素入栈。
同理 #503 Next Greater Element II
#84 Largest Rectangle in Histogram
只需要遍历所有直方柱,寻找以这个直方柱为高,可向左右扩展出的最大长方形面积,那么我们只需要找到对于每个 i ,其左/右边第一个比他矮的直方柱的下标,相当于就找到了以第 i 个直方柱为高,可向左右扩展的最远距离。
寻找左右第一个比h[i]
小的元素,可以使用上面的#1019的方法,如此一来只需要O(n)的时间复杂度
#85 Maximal Rectangle
相当于#84的扩展形式,依次把每一行当做底,每个位置的直方柱高相当于是从该位置向上延伸最大的连续1的个数。
#150 Evaluate Reverse Polish Notation
遍历数组,若是数字则入栈,若是运算符,则出栈两个数字,计算结果后将结果入栈
#155 Min Stack
在实际的元素栈之外,维护一个min stack,将当前栈内元素的最小值入栈
当元素栈进行pop操作时,min stack也进行pop,当元素栈push时,min stack也进行push,但push的是当前最小的元素(可以对比本次入栈元素与之前最小的元素)
#25 Reverse Nodes in k-Group
反转链表,记录3个指针即可
如果复杂度允许,可以递归,或者用栈
#138 Copy List with Random Pointer
因为有一个随机指针的存在,可能在复制时会多次访问同一个节点,所以需要额外用一个hash map记录所有已经复制过的节点,类似记忆化搜索
#142 Linked List Cycle II
判断链表中是否有环:
使用两个指针fast、slow,分别以2、1的前进速度从链表头开始遍历,若fast先遇到链表尾,则不含环,若两个指针相遇,说明存在环
证明:
当slow进入环时,fast必然已经在环的某一位上,此时开始fast开始在环上相对slow以1的速度运动,因此二者必然在slow绕环一周前相遇
判断链表中环的起点:
先用上面的方法找到fast与slow的相遇点p,使用两个指针a、b分别从链表头、相遇点p开始以相同速度开始遍历,则a、b相遇的位置为环的起点
证明:
假设链表非环段长为x,整个环长为c,当slow刚进入环时,fast已在环内运动距离为x(因为fast速度为slow的二倍,而此时slow移动了x),因此此时fast的位置相对于环起点(也就是slow目前的位置)为x % c,fast在移动方向上距离slow为c - x % c,由于fast相对于slow移动速度为1,因此再经过c - x % c次移动,fast与slow相遇,此时slow位置相对于环起点为c - x % c,也就是fast、slow相遇位置,从这一点再向前进x个位置的话,相对于环起点位置为(c + x - x % c) % c = 0
#146 LRU Cache
因为是Least Recently Used,所以用链表记录最近访问是最有效的,当某一个键值被访问时,将他在链表中的位置更新到表头,cache满时,将表位的键值删除。但由于链表的访问时间复杂度为O(n),因此需要额外用一个hash map记录每个键值在链表中的位置(iterator)
#148 Sort List
链表排序用插入(O(n^2))或者归并(O(nlogn))
#41 First Missing Positive
忽略大于数组长度n的正数,以及全部的非正数,将剩余的数字填入与其值对应的数组位置中,最后只需要遍历一遍数组,返回第一个下标不等于值的数字
#88 Merge Sorted Array
若可以使用额外空间,直接归并就可以,如果要求归并到其中一个长度为m + n的数组,可以从后向前归并
#135 Candy
从前向后扫一遍,保证ratings[i] > ratings[i - 1]的也满足candy[i] > candy[i - 1]
从后向前扫一遍,保证ratings[i] > ratings[i + 1]的也满足candy[i] > candy[i + 1]
#164 Maximum Gap
题目要求是找到无序数组的有序排列中相邻元素的最大差值
对于求最大Gap的题目,即使是大数据的题目,也可以使用分桶的方法来做
桶的数量必须大于元素的数量,这样一来,将所有元素放入对应的桶中,必然存在至少一个桶为空
最大的gap必然出现在连续的空桶两侧的两个桶内的元素之间,而不会出现在某个同内的元素之间(因为桶内元素间的Gap最大超不过桶宽,而桶间元素的差值可以超过这一值)
随后我们遍历一遍所有的桶,若存在某一个区间内的桶均为空,则找到该区间左侧第一个非空桶的最大值,以及该区间右侧第一个非空桶的最小值,这两个值的差即是最大Gap的一个候选值
#169 Majority Element
经典题目,要找出出现次数超过 n / k 的元素,记录 k - 1 个候选值(不可能出现超过k个出现次数大于 n / k 的元素),每个候选值对应一个计数器,遍历整个数组,若元素等于某个候选值,则候选值计数器加一,否则减一,若计数器小于0,则将对应的候选值变更为新元素,计数器初始化为 k - 1
这里的原理是,如果从整个数组中消去1个候选元素以及k - 1个其他元素,那么若候选元素在之前是符合要求的元素,他在消去k个元素后的数组中仍然会符合要求,因为(x - 1) / (n - k) > x / n > 1 / k
最后只需要确认一下各个候选值是不是真的符合要求即可
#189 Rotate Array
题目要求空间复杂度为O(1)
可以先将前半段和后半段分别翻转,再整体翻转,这样需要2次遍历
#523 Continuous Subarray Sum
计算数组0…i - 1的和sum[i]
,并将sum[i]
模k的结果存入哈希表中,之后只要发现新的sum[j] % k的值在哈希表中出现过,就说明(sum[j] - sum[i]) % k == 0
,相当于sum(a[i]...a[j]) = n * k
这道题有非常多的边界条件,需要非常小心
#525 Contiguous Array
和上面的#523属于同一种类型,即:当累加和重复出现某一模式时,说明两次重复之间的区间符合某一条件。这种题都可以考虑用哈希去记录累加和的模式,然后在遍历中用哈希确认模式是否重复出现
int findMaxLength(vector<int>& nums) {
unordered_map<int, int> mp;
int ans = 0;
mp[0] = -1;
for (int i = 0, sum = 0; i < nums.size(); ++i) {
sum += nums[i] == 1 ? 1 : -1;
if (mp.find(sum) != mp.end()) ans = max(ans, i - mp[sum]);
else mp[sum] = i;
}
return ans;
}
#560 Subarray Sum Equals K
和#523与#525一个类型,如果这道题规定所有数字都是正整数的话,可以用双滑动指针的方法来做,但是这道题涉及到负数,这种方法就不适用了
要统计有多少连续子数组满足某一个模式,都可以使用类似的方法
#128 Longest Consecutive Sequence
先将所有元素加入哈希表,然后遍历每个元素num,以其为中心向左右扩张连续区间:
若扩张到的元素存在于哈希表内,说明数组中有这个元素,可以扩张,扩张后将该元素从哈希表中删除,避免重复使用
若扩张到的元素不在哈希表内,说明数组中没有这个元素,不能扩张,扩张停止
这样一来,每个元素只会入表一次、出表一次,时间复杂度是O(n)的
class Solution {
public:
int longestConsecutive(vector<int> &num) {
if (num.size() == 0) return 0;
unordered_set<int> record(num.begin(),num.end());
int res = 1;
for(int n : num){
if(record.find(n)==record.end()) continue;
record.erase(n);
int prev = n-1,next = n+1;
while(record.find(prev)!=record.end()) record.erase(prev--);
while(record.find(next)!=record.end()) record.erase(next++);
res = max(res,next-prev-1);
}
return res;
}
};
#149 Max Points on a Line
用向量表示直线,将同一方向的向量表示成同一形式(比如令(x, y)的两个坐标互质),然后用hash map统计同一向量出现的次数即可
要注意重复点和垂直坐标轴的直线
#94 Binary Tree Inorder Traversal
递归遍历的话,没有什么难度,主要记一下非递归遍历的方法,用栈来辅助
另外两道,先序遍历 #144 Binary Tree Preorder Traversal,后序遍历 #145 Binary Tree Postorder Traversal 也是一样
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode *> stk;
vector<int> res;
if (!root) return res;
TreeNode *ptr = root;
while (!stk.empty() || ptr != NULL) {
if (ptr != NULL) {
stk.push(ptr);
res.push_back(ptr->val);
ptr = ptr->right;
}
else {
ptr = stk.top()->left;
stk.pop();
}
}
reverse(res.begin(), res.end());
return res;
}
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode *> stk;
vector<int> res;
if (!root) return res;
TreeNode *ptr = root;
while (!stk.empty() || ptr != NULL) {
if (ptr != NULL) {
stk.push(ptr);
ptr = ptr->left;
}
else {
TreeNode *node = stk.top();
stk.pop();
res.push_back(node->val);
ptr = node->right;
}
}
return res;
}
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode *> stk;
vector<int> res;
if (!root) return res;
TreeNode *ptr = root;
while (!stk.empty() || ptr != NULL) {
if (ptr != NULL) {
stk.push(ptr);
res.push_back(ptr->val);
ptr = ptr->left;
}
else {
ptr = stk.top()->right;
stk.pop();
}
}
return res;
}
};
#99 Recover Binary Search Tree
若变换BST中两个元素的位置,则在其中序遍历中,会有一个元素比其前后元素都小,另一个元素比其前后元素都大,这两个元素就是被调换的元素,把他们找出来就好了
可以在中序遍历时记录连续三个元素的值(实际实现中只需要记录前两个,正在遍历的元素就是第三个),比较中间值和两端值的大小
#105 Construct Binary Tree from Preorder and Inorder Traversal
递归,先序序列中的第一个节点是根节点,在中序序列中找到这个根节点的位置,则根节点左边的序列是左子树,右边的序列是右子树,进一步可以确定左右子树的序列长度,最后把问题划分为左右子树两个子问题。
类似的还有 #106 Construct Binary Tree from Inorder and Postorder Traversal ,把先序序列换成后序序列,本质方法是一样的
dist[i]
,则将源节点到节点 k 的所有路径(为这些路径添加一个新节点 i )添加到源节点到节点 i 的最短路中。#1020 Number of Enclaves
在矩阵外添加一圈陆地(可访问),然后Flood Fill搜索所有与外围陆地相连的陆地,用总的陆地数量减去floodfill结果,就可以得到孤岛面积
#200 Number of Islands
同上,Flood Fill
#199 Binary Tree Right Side View
树的层次遍历,用广搜
需要顺便记录层数,从而在输出的时候保证只输出没一层第一个/最后一个元素
#542 01 Matrix
矩阵中找到每个1最邻近的0,相当于岛上到海最近到距离,从岛开始搜的话,广搜每次只能搜一个点,因为广搜没办法更新中间节点,而深搜则复杂度不能接受。因此可以从海开始搜(因为海不需要更新,岛需要更新),并且初始加入队列的节点是所有的海面节点,而不是单独的一个。
remainder + (-negtiveBase)
,同时把商加一class Solution {
public:
string baseNeg2(int n) {
if (n == 0) return "0";
string res;
int negBase = -2;
while (n != 0) {
int remainder = n % negBase;
n /= negBase;
if (remainder < 0)
{
remainder += (-negBase);
n += 1;
}
res = to_string(remainder) + res;
}
return res;
}
};
for (int i = 5; n / i > 0; i *= 5) count += n / i;
#136 Single Number
所有数字按位异或,因为异或操作改变顺序不会影响结果,所以每2个相等的数字相互异或得到0,最后0与单一的数字异或得到单一数字本身
#137 Single Number II
如果是出现两次的话,用一个bit就可以
比如 b,初始为0
当5第1次出现时, b=5
当5第2次出现是, b清空为0,表示b可以去处理其他数字了
所以,最后 如果 b !=0的话,b记录的就是只出现了一次的那个数字
公式就是 b = b xor i 或者 b = b^i
那么,如果是三次的话,香浓定理,需要用2bits进行记录
当5第一次出现的时候,b = 5, a=0, b记录这个数字
当5第二次出现的时候,b = 0, a=5, a记录了这个数字
当5第三次出现的时候,b = 0, a=0, 都清空了,可以去处理其他数字了
所以,如果有某个数字出现了1次,就存在b中,出现了两次,就存在a中,所以返回 a|b
公式方面 ,上面两次的时候,b清空的公式是 b = b xor i
而第三次时,b要等于零,而这时a是True,所以再 & 一个a的非就可以,b = b xor & ~a
b = b xor i & ~a
a = a xor i & ~b
#191 Number of 1 Bits
消除末尾的1的位运算:n = n & (n - 1)
#201 Bitwise AND of Numbers Range
对m-n之间所有数字按位AND操作会使得m-n之间所有数字都相同的位保持不变,而m-n之间有任何两个数字不同的位变为0,因此我们只需要保持m-n之间所有数字都相同的高位不变,低位全部置为0就可以得到答案。
由于m和n分别是连续的这一串数字中最大和最小的数,因此m与n在高位相同的数位就是m-n之间所有数字全部一致的位(这部分若有任一位被置1,则会大于n,若有任意一位被置0,则会小于m,所以m-n之间所有数字的这部分数位都是一样的),余下的位置0即可
#2 Add Two Numbers
高精度加法
#29 Divide Two Integers
高精度除法
#43 Multiply Strings
高精度乘法
#31 Next Permutation
对于任意若干数字,其排列数的最后一种应该是降序排列的,这一规律是递归出现的。
若一数列的最低k位不是降序,则目前的排列一定还在这k位内进行;
若一数列的最后k位是降序,则这k位已经遍历了所有的排列数;
当数列的最后k位已遍历完所有排列数时,就要开始排列倒数第k-1位,此时的做法是将降序的num[-k:]
数列中大于且最接近于num[-(k - 1)]
的数字与num[-(k - 1)]
交换位置,并将num[-k:]
升序排列
#60 Permutation Sequence
依次计算1…n个数的全排列数,若发现A(n, n) > k,且A(n - 1, n - 1) < k,说明第k个排列序列发生在n个数的排列中,之后判断第k个排列序列发生在n个数排列中最高位为多少的排列、再判断次高位,依次类推,确定排列序列中所有的值。