0-还未做,先略过
1-待尝试其它方法
剑指 Offer 03. 数组中重复的数字
【数组,元素&索引的对应关系】
题干:
给定一个长度为n的数组nums,里面的所有数字都在 [ 0 , n − 1 ] [0,n-1] [0,n−1]内,找出数组中任意一个重复数字并返回。
数据范围:2<=n<=1e5
函数签名:
int findRepeatNumber(vector<int>& nums);
解:
for循环版:
int findRepeatNumber(vector<int>& nums) {
for(int i = 0; i< nums.size(); i++){
while(nums[i]!= i){
if(nums[nums[i]] == nums[i]){
return nums[i];
}
swap(nums[i], nums[nums[i]]);
}
}
return -1;
}
while循环版:
int findRepeatNumber(vector<int>& nums) {
int i = 0;
while(i < nums.size()){
if(nums[i] == i){
++i;
continue;
}
if(nums[nums[i]] == nums[i]) return nums[i];
swap(nums[i], nums[nums[i]]);
}
return -1;
}
剑指 Offer 04. 二维数组中的查找
【二分&抽象BST】
题干:给定一个m*n的二维数组,每行按从左到右非递减,每列从上到下非递减的顺序排序。给定一个整数target,判断数组中是否含有该整数。
数据范围:0<=m,n<=1e3
函数签名:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target)
解:
参考
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(!matrix.size() || !matrix[0].size()) return false;
int m = matrix.size(), n = matrix[0].size();
for(int i = 0; i < m; i++){
int left = 0, right = n;
int index = 0;
while(left < right){
int mid = left + ((right - left) >> 1);
if(matrix[i][mid]== target) return true;
else if(matrix[i][mid] > target) right = mid;
else left = mid+1;
}
}
return false;
}
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(!matrix.size() || !matrix[0].size()) return false;
int m = matrix.size(), n = matrix[0].size();
int i = 0, j = n-1;
while(i < m && j >= 0){
if(target == matrix[i][j]) return true;
else if(target < matrix[i][j]) j--;
else i++;
}
return false;
}
剑指 Offer 05. 替换空格
【双指针】
题干:把字符串s的每个空格替换成"%20"
数据范围:0<=s.length()<=1e5
函数签名:
string replaceSpace(string s);
解:
string replaceSpace(string s) {
string res;
int j = 0;
for(char c: s){
if(c == ' ') res+="%20";
else res += c;
}
return res;
}
s.length()+2
) string replaceSpace(string s) {
int cnt = 0;
for(char c: s){
if(c == ' ') ++cnt;
}
int len = s.size();
s.resize(len + 2*cnt);
int i = len-1, j = s.size()-1;
while(i != j){
if(s[i] != ' '){
s[j] = s[i];
i--, j--;
}
else{
s[j--] = '0';
s[j--] = '2';
s[j--] = '%';
i--;
}
}
return s;
}
剑指 Offer 06. 从尾到头打印链表
【递归,二叉树后序遍历】
题干:输入一个链表的头节点,从尾到头反过来返回每个节点的值。
数据范围:0<=链表长度<=1e5
函数签名:
vector<int> reversePrint(ListNode* head)
解:
vector<int> reversePrint(ListNode* head) {
vector<int> res;
printLink(head, res);
return res;
}
void printLink(ListNode* head, vector<int>& res){
if(!head) return;
printLink(head->next, res);
res.push_back(head->val);
}
剑指 Offer 07. 重建二叉树
【递归】
题干:通过二叉树的前序序列和中序序列重建二叉树
数据范围:0<=节点个数<=5e3
函数签名:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
解:
unordered_map<int, int> mp;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
for(int i = 0; i < inorder.size(); i++) mp[inorder[i]] = i;
int n = preorder.size();
return build(preorder, 0, n-1, inorder, 0, n-1);
}
TreeNode* build(vector<int>& preorder, int preL, int preR, vector<int>& inorder, int inL, int inR){
if(preL > preR) return nullptr;
int rootVal = preorder[preL];
TreeNode* root = new TreeNode(rootVal);
int inRootIndex = mp[rootVal];
int leftSize = inRootIndex - inL;
root->left = build(preorder, preL+1, preL+1 + leftSize-1, inorder, inL, inRootIndex-1);
root->right = build(preorder, preL+ leftSize + 1, preR, inorder, inRootIndex+1, inR);
return root;
}
剑指 Offer 09. 用两个栈实现队列
【模拟】
题干:用两个栈实现队列的push和pop操作(pop不成功返回-1)
数据范围:1<=val<=1e4
,最多进行1e4
次操作
函数签名:
class CQueue {
public:
CQueue() {}
void appendTail(int value) {
}
int deleteHead() {
}
};
解:
class CQueue {
private:
stack<int> st1, st2;
public:
CQueue() {}
void appendTail(int value) {
st1.push(value);
}
int deleteHead() {
if(!st2.empty()){
int val = st2.top();
st2.pop();
return val;
}
while(!st1.empty()){
int val = st1.top();
st1.pop();
st2.push(val);
}
if(st2.empty()) return -1;
int val = st2.top();
st2.pop();
return val;
}
};
剑指 Offer 10- I. 斐波那契数列
【递归,递推,矩阵快速幂(待整理)】
题干:求斐波那契数列(f[0]=0,f[1]=1, f[n]=f[n-1]+f[n-2]
),并对答案取模(1e9+7)
数据范围:0<=n<=100
函数签名:
int fib(int n);
解:
const int mod = 1e9+7;
static const int N = 100;
int f[N+2];
int fib(int n){
if(n == 0) return 0;
if(n == 1) return 1;
if(f[n]) return f[n];
f[n] = (fib(n-1) + fib(n-2)) % mod;
return f[n];
}
const int mod = 1e9+7;
static const int N = 100;
int f[N+2];
int fib(int n) {
int f[n+2];
f[0] = 0;
f[1] = 1;
for(int i = 2; i <= n; i++){
f[i] = (f[i-1] + f[i-2]) % mod;
}
return f[n];
}
剑指 Offer 10- II. 青蛙跳台阶问题
【递归,递推,矩阵快速幂(待整理)】
题干:青蛙往上跳台阶,一次可以跳1级也可以跳2级,求青蛙跳上一个n级的台阶共有多少种跳法,答案对1e9+7取模。
数据范围:0<=n<=100
函数签名:
int numWays(int n);
解:
const int mod = 1e9+7;
static const int N = 100;
int dp[N+5];
int numWays(int n) {
if(n == 0) return 1;
if(n == 1) return 1;
if(dp[n]) return dp[n];
dp[n] = (numWays(n-1) + numWays(n-2)) % mod;
return dp[n];
}
const int mod = 1e9+7;
static const int N = 100;
int dp[N+5];
int numWays(int n){
dp[0]=1;
dp[1]=1;
for(int i = 2; i<= n; i++){
dp[i] = (dp[i-1] + dp[i-2]) % mod;
}
return dp[n];
}
const int mod = 1e9+7;
int numWays(int n){
int dp_0 = 1, dp_1 = 1;
for(int i = 2; i<= n; i++){
int dp_2 = (dp_0 + dp_1) % mod;
dp_0 = dp_1;
dp_1 = dp_2;
}
return dp_1;
}
剑指 Offer 11. 旋转数组的最小数字
【二分】
题干:给定一个不降序数组numbers
(里面可以有相同元素),现将numbers
从 k k k位置( k k k未知)旋转(即将从 k k k开始到末尾的部分移到最前面),要求在numbers
中寻找到最小的元素并返回元素值。
数据范围:1<=numbers.length<=5e3, -5e3<=numbers[i]<=5e3
函数签名:
int minArray(vector<int>& numbers);
解:
int minArray(vector<int>& numbers) {
int left = 0, right = numbers.size()-1;
if(left == right) return numbers[0]; // boundary
while(numbers[left] == numbers[right] && (left != right)) left++;
int target = numbers[right];
right++;
int ans=0;
while(left < right){
int mid = left + ((right - left)>>1);
if(numbers[mid]<=target){
ans = numbers[mid];
right = mid;
}
else left = mid + 1;
}
return ans;
}
剑指 Offer 12. 矩阵中的路径
【DFS回溯】
题干:给定一个二维字符网格board和一个字符串word,判断word是否在board中存在,存在返回true,不存在返回false。
数据范围:1<=m,n<=6, 1<=word.length()<=15
函数签名:
bool exist(vector<vector<char>>& board, string word);
解:
bool exist(vector<vector<char>>& board, string word) {
m = board.size(), n = board[0].size();
for(int i = 0; i < m;i ++){
for(int j = 0; j < n; j++){
if(board[i][j] == word[0]){
if(dfs(board, word, i, j, 0)) return true;
}
}
}
return false;
}
private:
bool vis[10][10];
int m, n;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
bool dfs(vector<vector<char>>& board, string word, int x, int y, int pos){
if(pos == word.size()) return true;
if(x<0 || x >= m || y<0 || y >= n){
return false;
}
if(vis[x][y] || word[pos] != board[x][y]) return false;
vis[x][y] = 1;
for(int i = 0; i<= 3; i++){
int newX = x + dx[i];
int newY = y + dy[i];
if(dfs(board, word, newX, newY, pos+1)){
return true;
}
}
vis[x][y] = 0;
return false;
}
剑指 Offer 14- I. 剪绳子
【DP,贪心+快速幂】
题干:给定一根长度为n的绳子,把它剪成整数长度的m段(m必须大于1),问这m的乘积最大可能是多少?
数据范围:2<=n<=58
函数签名:
int cuttingRope(int n);
解:
int cuttingRope(int n){
return dp(n);
}
private:
int memo[60];
int dp(int n){
if(n==0 || n==1) return 0;
if(memo[n]) return memo[n];
for(int i = 1; i < n; i++){
memo[n] = max(memo[n], max(i*(n-i), i*dp(n-i)));
}
return memo[n];
}
int cuttingRope(int n){
vector<int> dp(n+2);
for(int i = 2; i <= n; i++){
for(int j = 1; j < i; j++){
dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j]));
}
}
return dp[n];
}
剑指 Offer 14- II. 剪绳子 II
【贪心+快速幂】
解:
const int p = 1e9+7;
long long qmod(long long a, long long b){
long long res = 1;
while(b){
if(b&1) res = res * a %p;
a = a * a %p;
b>>=1;
}
return res;
}
int cuttingRope(int n) {
if(n == 2) return 1;
if(n == 3) return 2;
int cnt = n/3;
int mod = n%3;
int ans = 1;
if(!mod){
ans = qmod(3ll, cnt) %p;
}
else if(mod == 1){
ans = qmod(3ll, cnt-1) * 4ll % p;
}
else{
ans = qmod(3ll, cnt) * 2ll % p;
}
return ans;
}
剑指 Offer 15. 二进制中1的个数
【位运算】
解:
int hammingWeight(uint32_t n) {
int cnt = 0;
while(n){
if(n&1) ++cnt;
n>>=1;
}
return cnt;
}
int hammingWeight(uint32_t n) {
int cnt = 0;
while(n){
++cnt;
n = n & (n-1);
}
return cnt;
}
剑指 Offer 16. 数值的整数次方
【快速幂,浮点数】
解:
double myPow(double x, int n){
if(!x) return 0;
long long b = n;
if(n < 0){
x = 1.0/x;
b = -b;
}
double res = 1.0;
while(b){
if(b & 1) res = res * x;
x = x * x;
b >>=1;
}
return res;
}
剑指 Offer 17. 打印从1到最大的n位数
【dfs,全排列,元素无重可复选】
解:
方法:递归,DFS全排列
大数,利用string转int。
int转string:to_string
string s = to_string(x);
string转int:atoi+c_str
大数,利用string。转int需要去除前导0,所以用atoi+s.c_str()转为int
区别:
int x = atoi(s.c_str());
//先将string转为const char*,再转为int
bool flag = 0;
vector<int> ans;
vector<int> printNumbers(int n) {
string s = "0123456789";
string path = "";
dfs(s, path, n);
return ans;
}
void dfs(string &s, string &path, int res){
if(res == 0){
if(!flag){ //去掉0
flag = 1;
return;
}
ans.push_back(atoi(path.c_str()));
return;
}
for(int i = 0; i< s.length(); i++){
path += s[i]; //path.push_back(s[i]);
dfs(s, path, res-1);
path.pop_back();
}
}
剑指 Offer 18. 删除链表的节点
【链表删除】
解:
判断的是cur->next是否为要删除的节点,注意判空
ListNode* deleteNode(ListNode* head, int val) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* cur = dummy;
while(cur && cur->next){ //while(cur->next)
if(cur->next->val == val){
cur->next = cur->next->next;
break;
}
cur = cur->next;
}
return dummy->next;
}
剑指 Offer 19. 正则表达式匹配
【DP】
解:
int m, n;
int memo[30+5][30+5];
bool isMatch(string s, string p) {
m = s.length(), n = p.length();
memset(memo, -1, sizeof(memo));
return dp(s, p, 0, 0);
}
bool dp(string &s, string &p, int i, int j){
if(j >= n){
return i == m;
}
if(i >= m){
if((n-j) & 1) return false;
for(int k = j; k+1 < n; k+=2){
if(p[k+1] != '*') return false;
}
return true;
}
if(memo[i][j]!= -1) return memo[i][j];
bool res;
if(s[i] == p[j] || p[j]=='.'){
if(j < n-1 && p[j+1] == '*'){
res= dp(s, p, i, j+2) | dp(s, p, i+1, j);
}
else{
res= dp(s, p, i+1, j+1);
}
}
else{
if(j < n-1 && p[j+1]=='*'){
res= dp(s, p, i, j+2);
}
else res=false;
}
memo[i][j] = res;
return memo[i][j];
}
};
/*
dp(i,j)
1.s[i]==p[j] || p[j]=='.'
(1)p[j+1] =='*'
i,j+=2
i++,j
(2)others
i++,j++
2.s[i]!=p[j]
(1)p[j+1]=='*'
i,j+=2
(2)others
return false
*/
剑指 Offer 20. 表示数值的字符串
【匹配,有限状态自动机(待整理)】
if(a) return true;
else return false;
->
return a;
解:
bool isNumber(string s){
int i = 0, j = s.length()-1;
// 去掉最前方和最后方空格
while(i < s.length() && s[i]==' ') ++i;
while(j > 0 && s[j] == ' ') --j;
bool flagE = 0;
int k = i;
for(; k <= j; k++){
if(!isValid(s[k])) return false;
if(s[k] == 'e' || s[k] == 'E'){
flagE = 1;
break;
}
}
if(flagE){ //有e,e前面可以是整数/小数,e后面只能是整数
return ((isInt(s, i, k-1) || isDec(s, i, k-1)) && isInt(s, k+1, j));
}
return (isInt(s, i, j) || isDec(s, i, j)); //没有e,判断整个字符串是否是整数/小数
}
bool isValid(char c){ //字符是否为合法字符
if(c == '+' || c == '-' || c == 'e' || c == 'E' || c == '.') return true;
if(c >= '0' && c <= '9') return true;
return false;
}
bool isSign(char c){ //字符是否为符号
return (c == '+' || c == '-');
}
bool isNum(char c){ //字符是否为数字
return (c >= '0' && c <= '9');
}
bool isInt(string &s, int i, int j){ // 字符串是否为整数
if(i < 0 || j >= s.length()) return false;
if(isSign(s[i])) ++i;
bool flag = 0;
for(int k = i; k<= j; k++){
if(!isNum(s[k])) return false;
flag = 1;
}
return flag;
}
bool isIntNoSign(string &s, int i, int j){ //字符串是否为不带符号整数
if(i < 0 || j >= s.length()) return false;
bool flag = 0;
for(int k = i; k<= j; k++){
if(!isNum(s[k])) return false;
flag = 1;
}
return flag;
}
bool isDec(string &s, int i, int j){ //字符串是否为小数
if(i < 0 || j >= s.length()) return false;
if(isSign(s[i])) ++i;
int k = i;
for(; k<= j; k++){
if(s[k] == '.') break;
}
if(k == i && isIntNoSign(s, k+1, j)) return true; //.开头,后面必须为不带符号整数
else if(k == j && isInt(s, i, k-1)) return true; //.结尾,前面必须为整数
else if(isInt(s, i, k-1) && isIntNoSign(s, k+1, j)) return true; //.在中间,前后必须为整数,其中后面必须为不带符号整数
return false;
}
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
【双指针】
vector<int> exchange(vector<int>& nums) {
int i = 0, j = nums.size() - 1;
while(i < j){
// cout<
if(!(nums[i] & 1) && (nums[j]&1)){
swap(nums[i], nums[j]);
i++;
j--;
continue;
}
if(nums[i] & 1) i++;
if(!(nums[j] & 1)) j--;
}
return nums;
}
剑指 Offer 22. 链表中倒数第k个节点
【双指针-快慢指针】
解:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode * fast = head, *slow = head;
while(k && fast){
fast = fast->next;
k--;
}
while(fast){
fast = fast->next;
slow = slow->next;
}
return slow;
}
剑指 Offer 24. 反转链表
【迭代&递归】
迭代版:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head, *prev = nullptr;
while(cur){
ListNode* tmp = cur->next;
cur->next = prev;
prev = cur;
cur = tmp;
}
return prev;
}
递归版:
ListNode* reverseList(ListNode* head){
if(!head || !head->next) return head;
ListNode* last = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return last;
}
剑指 Offer 25. 合并两个排序的链表
【迭代&递归】
迭代版:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
while(l1 && l2){
if(l1->val <= l2->val){
cur->next = l1;
l1 = l1->next;
}
else{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
cur->next = l1? l1: l2;
return dummy->next;
}
递归版:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2){
if(!l1) return l2;
if(!l2) return l1;
if(l1->val <= l2->val){
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else{
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
剑指 Offer 26. 树的子结构
【递归,二叉树】
解:
bool isSubStructure(TreeNode* A, TreeNode* B) {
if(!A || !B) return false;
if(isSubStru(A, B)) return true;
return isSubStructure(A->left, B) || isSubStructure(A->right, B);
}
bool isSubStru(TreeNode* A, TreeNode* B){
if(!B) return true;
if(!A || A->val != B->val) return false;
return isSubStru(A->left, B->left) && isSubStru(A->right, B->right);
}
剑指 Offer 27. 二叉树的镜像
【递归,二叉树】
TreeNode* mirrorTree(TreeNode* root) {
if(!root) return nullptr;
TreeNode* left = mirrorTree(root->left);
TreeNode* right = mirrorTree(root->right);
root->left = right;
root->right = left;
return root;
}
剑指 Offer 28. 对称的二叉树
【递归,二叉树】
判断一棵树是否为对称二叉树
解:
bool isSymmetric(TreeNode* root) {
if(!root) return true;
return isSymmetric(root, root);
}
bool isSymmetric(TreeNode* T1, TreeNode* T2){
if(!T1 && !T2) return true;
if(!T1 || !T2) return false;
if(T1->val != T2->val) return false;
return isSymmetric(T1->left, T2->right) && isSymmetric(T1->right, T2->left);
}
剑指 Offer 29. 顺时针打印矩阵
【模拟,花式遍历数组】
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if(!matrix.size() || !matrix[0].size()) return {};
int left = 0, high = 0, bottom = matrix.size()-1, right = matrix[0].size()-1;
vector<int> res;
while(high <= bottom && left <= right){
for(int i = left; i<= right; i++){
res.push_back(matrix[high][i]);
}
++high;
if(high > bottom) break;
for(int i = high; i<= bottom; i++){
res.push_back(matrix[i][right]);
}
--right;
if(right < left) break;
for(int i = right; i>= left; i--){
res.push_back(matrix[bottom][i]);
}
--bottom;
if(bottom < high) break;
for(int i = bottom; i>= high; i--){
res.push_back(matrix[i][left]);
}
++left;
if(left > right) break;
}
return res;
}
剑指 Offer 30. 包含min函数的栈
【辅助栈】
每次调用min函数都需要得到当前栈中的最小值,可借助第二个栈来解决。
解:
class MinStack {
public:
/** initialize your data structure here. */
stack<int> st1, st2;
MinStack() {}
void push(int x) {
if(st2.empty() || st2.top() >= x) st2.push(x);
st1.push(x);
}
void pop() {
if(st2.top() == st1.top()) st2.pop();
st1.pop();
}
int top() {
return st1.top();
}
int min() {
return st2.top();
}
};
class MinStack {
public:
/** initialize your data structure here. */
stack<int> st;
int minn = INT_MAX;
MinStack() {}
void push(int x) {
st.push(minn);
if(x < minn) minn = x;
st.push(x);
}
void pop() {
st.pop();
minn = st.top();
st.pop();
}
int top() {
return st.top();
}
int min() {
return minn;
}
};
剑指 Offer 31. 栈的压入、弹出序列
【模拟】
解:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped){
stack<int> st;
int i = 0;
for(int num: pushed){
st.push(num);
while(!st.empty() && st.top() == popped[i]){
st.pop();
i++;
}
}
return st.empty();
}
剑指 Offer 32 - I. 从上到下打印二叉树
【BFS,二叉树层序遍历】
vector<int> levelOrder(TreeNode* root) {
if(!root) return {};
vector<int> res;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
int size = q.size();
for(int i = 0; i< size; i++){
TreeNode* t = q.front();
q.pop();
res.push_back(t->val);
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
}
return res;
}
剑指 Offer 32 - II. 从上到下打印二叉树 II
【BFS,二叉树层序遍历】
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
if(!root) return ans;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
int size = q.size();
vector<int> res;
for(int i = 0; i< size; i++){
TreeNode* t = q.front();
q.pop();
res.push_back(t->val);
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
ans.push_back(res);
}
return ans;
}
剑指 Offer 32 - III. 从上到下打印二叉树 III
【BFS,层序遍历二叉树】
解:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
if(!root) return ans;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
int size = q.size();
deque<int> dq;
for(int i = 0; i< size; i++){
TreeNode* t = q.front();
q.pop();
if(ans.size()&1) dq.push_front(t->val);
else dq.push_back(t->val);
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
ans.push_back(vector<int>{dq.begin(), dq.end()});
}
return ans;
}
剑指 Offer 33. 二叉搜索树的后序遍历序列
【递归&单调栈】
解:
bool verifyPostorder(vector<int>& postorder) {
return isValid(postorder, 0, postorder.size()-1);
}
bool isValid(vector<int>& postorder, int i, int j){
if(i >= j) return true;
int p = i;
while(postorder[p] < postorder[j]) p++;
int k = p;
while(postorder[p] > postorder[j]) p++;
if(p != j) return false;
return isValid(postorder, i, k-1) && isValid(postorder, k, j-1);
}
bool verifyPostorder(vector<int>& postorder) {
stack<int> st;
int root = INT_MAX;
for(int i = postorder.size()-1; i >= 0; i--){
if(postorder[i] > root) return false;
while(!st.empty() && st.top() > postorder[i]){
root = st.top();
st.pop();
}
st.push(postorder[i]);
}
return true;
}
剑指 Offer 34. 二叉树中和为某一值的路径
【递归,二叉树先序遍历】
解:
vector<vector<int>> ans;
vector<vector<int>> pathSum(TreeNode* root, int target) {
if(!root) return ans;
vector<int>res;
dfs(res, root, target);
return ans;
}
void dfs(vector<int> & res,TreeNode* root, int target){
if(!root) return;
target -= root->val;
res.push_back(root->val);
if(!root->left && !root->right){
if(!target) ans.push_back(res);
}
dfs(res, root->left, target);
dfs(res, root->right, target);
res.pop_back();
target += root->val;
return;
}
剑指 Offer 35. 复杂链表的复制
【哈希&链表的拼接与拆分】
解:
unordered_map mp;
mp[head];
Node* copyRandomList(Node* head) {
unordered_map<Node*, Node*> mp;
Node* cur = head;
while(cur){
mp[cur] = new Node(cur->val);
cur = cur->next;
}
cur = head;
while(cur){
Node* tmp = mp[cur];
tmp->next = mp[cur->next];
tmp->random = mp[cur->random];
cur = cur->next;
}
return mp[head];
}
cur->next->random = cur->random? cur->random->next: nullptr;
prev->next = prev->next->next; cur->next = cur->next->next
,然后迭代:prev = prev->next; cur = cur->next;
,直到cur->next为空为止。 Node* copyRandomList(Node* head) {
if(!head) return nullptr;
Node* cur = head;
while(cur){
Node* tmp = new Node(cur->val);
tmp->next = cur->next;
cur->next = tmp;
cur = tmp->next;
}
cur = head;
while(cur){
cur->next->random = cur->random? cur->random->next: nullptr;
cur = cur->next->next;
}
Node* prev = head, *newHead = head->next;
cur = newHead;
while(cur->next){
prev->next = prev->next->next;
cur->next = cur->next->next;
prev = prev->next;
cur = cur->next;
}
prev->next = nullptr; //carefull!
return newHead;
}
剑指 Offer 36. 二叉搜索树与双向链表
【递归,二叉树中序遍历】
解:
Node* prev;
Node* treeToDoublyList(Node* root) {
if(!root) return nullptr;
prev = new Node(-1);
Node* head = prev;
traverse(root);
head = head->right;
prev-> right = head;
head-> left = prev;
return head;
}
void traverse(Node* root){
if(!root) return;
traverse(root->left);
prev -> right = root;
root-> left = prev;
prev = root;
traverse(root->right);
}
//另一种对头节点和尾节点的操作方法:
Node* prev, *head;
Node* treeToDoublyList(Node* root) {
if(!root) return nullptr;
traverse(root);
prev-> right = head;
head-> left = prev;
return head;
}
void traverse(Node* root){
if(!root) return;
traverse(root->left);
if(!head) head = root;
else prev -> right = root;
root-> left = prev;
prev = root;
traverse(root->right);
}
剑指 Offer 37. 序列化二叉树
【递归】
剑指 Offer 38. 字符串的排列
【DFS,全排列,元素可重不可复选】
int vis[10]; //vector vis(s.length(), false);
vector<string> ans;
string path;
vector<string> permutation(string s) {
sort(s.begin(), s.end());
dfs(s);
return ans;
}
void dfs(string& s){
if(path.length() == s.length()){
ans.push_back(path);
return;
}
char prev = '0';
for(int i = 0; i < s.length(); i++){
if(vis[i]) continue;
if(s[i] == prev) continue;
prev = s[i];
vis[i] = 1;
path += s[i];
dfs(s);
vis[i] = 0;
path.pop_back();
}
}
剑指 Offer 39. 数组中出现次数超过一半的数字
【哈希表&摩尔投票法&快速选择】
解:
int majorityElement(vector<int>& nums) {
int vote = 0;
int number;
for(int num: nums){
if(!vote) number = num;
vote += (num == number)? 1: -1;
}
return number; //该题保证出现次数超过一半的数字一定存在
// 验证:number是否是超过一半的数字。
// 因为使用摩尔投票法得到正确结果的前提就是该数字必须在数组中出现的次数在一半以上。
// int cnt = 0;
// for(int num: nums){
// if(num == number) ++cnt;
// }
// return (cnt > nums.size()/2)? number: -1;
}
剑指 Offer 40. 最小的k个数
【快速排序&快速选择】
//快速排序后选取前k个元素
vector<int> getLeastNumbers(vector<int>& arr, int k){
if(k >= arr.size()) return arr;
quickSort(arr, 0, arr.size()-1);
vector<int> res({arr.begin(), arr.begin()+k});
return res;
}
void quickSort(vector<int> &arr, int l, int r){
if(l >= r) return;
int p = partition(arr, l, r);
quickSort(arr, l, p-1);
quickSort(arr, p+1, r);
}
int partition(vector<int> & arr, int l, int r){
int pivot = arr[l];
int i = l+1, j = r; //[l,i), (j, r]
while(i <= j){
while(i <= r && arr[i] <= pivot) i++;
while(j > l && arr[j] > pivot) j--;
if(i > j) break;
swap(arr[i], arr[j]);
}
swap(arr[l], arr[j]);
return j;
}
迭代:
// 借助快排,对于一个分界点,它前面的元素都比它小(或等于),它后面的元素都比它大。
// 所以当分界位置==k时(下标从0开始,所以它是第k+1小元素),它前面的k个元素就是前k小元素。
vector<int> getLeastNumbers(vector<int>& arr, int k){
if(k >= arr.size()) return arr;
return quickSort(arr, 0, arr.size()-1, k); //[l,r]
}
vector<int> quickSort(vector<int>& arr, int l, int r, int k){
vector<int> res;
while(l <= r){
int p = partition(arr, l, r);
if(p == k){
res.assign(arr.begin(), arr.begin()+k);
break;
}
else if(p < k) l = p + 1; //[p+1, r]
else r = p - 1; //[l, p-1]
}
return res;
}
int partition(vector<int> &arr, int l, int r){
int pivot = arr[l];
int i = l+1, j = r;
while(i <= j){
while(i <= r && arr[i] <= pivot) i++;
while(j > l && arr[j] > pivot) j--;
if(i > j) break;
swap(arr[i], arr[j]);
}
swap(arr[l], arr[j]);
return j;
}
递归:
// 同上思路,递归写法
vector<int> res;
vector<int> getLeastNumbers(vector<int>& arr, int k){
if(k >= arr.size()) return arr;
quickSort(arr, 0, arr.size()-1, k);
return res;
}
void quickSort(vector<int>& arr, int l, int r, int k){ //[l,r]
if(l > r) return;
int p = partition(arr, l, r);
if(p == k){
res.assign(arr.begin(), arr.begin()+k);
return;
}
else if(p < k) quickSort(arr, p+1, r, k);
else quickSort(arr, l, p-1, k);
}
int partition(vector<int>& arr, int l, int r){
int pivot = arr[l];
int i = l+1, j = r;
while(i <= j){
while(i <= r && arr[i] <= pivot) i++;
while(j > l && arr[j] > pivot) j--;
if(i > j) break;
swap(arr[i], arr[j]);
}
swap(arr[l], arr[j]);
return j;
}
剑指 Offer 41. 数据流中的中位数
剑指 Offer 42. 连续子数组的最大和
【滑动窗口&线性DP&贪心+前缀和】
解:
int maxSubArray(vector<int>& nums) {
int left = 0, right = 0;
int windowSum = 0;
int maxSum = INT_MIN;
while(right < nums.size()){
windowSum += nums[right];
right++;
maxSum = max(maxSum, windowSum); //必须写在前面,是在每次扩大窗口的时候更新答案。若写在缩小窗口的后面,有可能把窗口大小缩为0,这样更新的答案就不是正确的
while(windowSum < 0 && left < right){
windowSum -= nums[left];
left++;
}
}
return maxSum;
}
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size(), 0);
dp[0] = nums[0];
int maxSum = dp[0];
for(int i = 1; i < nums.size(); i++){
dp[i] = max(dp[i-1], 0)+nums[i];
maxSum = max(maxSum, dp[i]);
}
return maxSum;
}
//空间压缩优化后
int maxSubArray(vector<int>& nums){
int dp_0 = nums[0];
int maxSum = dp_0;
for(int i = 1;i < nums.size(); i++){
int dp_1 = max(dp_0 + nums[i], nums[i]);
dp_0 = dp_1;
maxSum = max(maxSum, dp_0);
}
return maxSum;
}
int maxSubArray(vector<int>& nums){
vector<int> preSum(nums.size(), 0);
preSum[0] = nums[0];
for(int i = 1; i < nums.size(); i++){
preSum[i] = preSum[i-1] + nums[i];
}
int minPreSum = 0;
int maxSum = INT_MIN;
for(int i = 0; i < nums.size(); i++){
int sum = preSum[i] - minPreSum;
maxSum = max(maxSum, sum);
minPreSum = min(minPreSum, preSum[i]);
}
return maxSum;
}
剑指 Offer 43. 1~n 整数中 1 出现的次数
【数学】
剑指 Offer 44. 数字序列中某一位的数字
【数学】
剑指 Offer 46. 把数字翻译成字符串
【DP】
题干:
给定一个非负数,可进行分割,但每次分割出来的数必须 ≤ 25 \le 25 ≤25,问共有多少种不同的分割方式。
如:2163。可分割:[2,1,6,3],[21,6,3],[2,16,3] 3种
数据范围: 0 ≤ n u m ≤ 2 31 0 \le num \le 2^{31} 0≤num≤231
函数签名:
int translateNum(int num);
解:dp
方法1:dp,递归
int translateNum(int num){
string str = to_string(num);
return dp(str, 0);
}
int dp(string &str, int i){
if(i >= str.length()-1) return 1;
string t = str.substr(i, 2);
if(t[0] == '1' || t[0] == '2' && t[1] <= '5'){
return dp(str, i+2) + dp(str, i+1);
}
return dp(str, i+1);
}
使用记忆化搜索:
int memo[15];
int translateNum(int num){
string str = to_string(num);
memset(memo, -1, sizeof(memo));
return dp(str, 0);
}
int dp(string &str, int i){
if(i >= str.length()-1) return 1;
if(memo[i] != -1) return memo[i];
string t = str.substr(i, 2);
if(t >= "10" && t <= "25"){
memo[i] = dp(str, i+2) + dp(str, i+1);
}
else memo[i] = dp(str, i+1);
return memo[i];
}
方法2:dp,递推
int translateNum(int num){
string str = to_string(num);
int n = str.length();
vector<int> dp(n+5, 0);
dp[n-1] = 1, dp[n] = 1;
for(int i = n-2; i >= 0; i--){
string t = str.substr(i, 2);
if(t >= "10" && t <= "25") dp[i] = dp[i+2] + dp[i+1];
else dp[i] = dp[i+1];
}
return dp[0];
}
//正向遍历
int translateNum(int num){
string str = to_string(num);
int n = str.length();
vector<int> dp(n+5, 0);
dp[0] = 1, dp[1] = 1;
for(int i = 1; i < n; i++){
string t = str.substr(i-1,2);
if(t >= "10" && t <= "25") {
if(i-2 < 0) dp[i] = 1 + dp[i-1];
else dp[i] = dp[i-2] + dp[i-1];
}
else dp[i] = dp[i-1];
}
return dp[n-1];
}
空间压缩优化:
int translateNum(int num){
string str = to_string(num);
int n = str.length();
int dp_0 = 1, dp_1 = 1;
for(int i = n-2; i>= 0; i--){
string t = str.substr(i, 2);
int dp_2;
if(t >= "10" && t <="25") dp_2 = dp_0 + dp_1;
else dp_2 = dp_0;
dp_1= dp_0;
dp_0 = dp_2;
}
return dp_0;
}
利用整数取余和除法:
int translateNum(int num){
int dp_0 = 1, dp_1 = 1;
while(num){
int t = num%100;
int dp_2;
if(t >= 10 && t <= 25){
dp_2 = dp_0 + dp_1;
}
else dp_2 = dp_0;
dp_1 = dp_0;
dp_0 = dp_2;
num /= 10;
}
return dp_0;
}
方法3:递归
int translateNum(int num){
return dp(num);
}
int dp(int num){
if(!num) return 1;
// if(num < 10) return 1;
int t = num%100;
if(t >= 10 && t <= 25) return dp(num/10) + dp(num/100);
return dp(num/10);
}
剑指 Offer 47. 礼物的最大价值
【线性DP】
最大路径和
解:
int maxValue(vector<vector<int>>& grid) {
if(!grid.size()) return 0;
int m = grid.size(), n = grid[0].size();
return dp(grid, m-1, n-1);
}
int memo[200+5][200+5];
int dp(vector<vector<int>>&grid, int i, int j){
if(i < 0 || j < 0) return 0;
if(memo[i][j]) return memo[i][j];
memo[i][j] = max(dp(grid, i-1, j), dp(grid, i, j-1)) + grid[i][j];
return memo[i][j];
}
int maxValue(vector<vector<int>>& grid) {
if(!grid.size()) return 0;
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dp(m+2, vector<int>(n+2, 0));
dp[0][0] = grid[0][0];
for(int i = 1; i < m; i++) dp[i][0] = dp[i-1][0] + grid[i][0];
for(int j = 1; j < n; j++) dp[0][j] = dp[0][j-1] + grid[0][j];
for(int i = 1; i < m; i++){
for(int j = 1; j < n; j++){
dp[i][j] = max(dp[i][j-1], dp[i-1][j]) + grid[i][j];
}
}
return dp[m-1][n-1];
}
//优化
int maxValue(vector<vector<int>>& grid){
if(!grid.size()) return 0;
int m = grid.size(), n = grid[0].size();
vector<int> dp(n+2, 0);
for(int i = 0; i< m; i++){
dp[0] += grid[i][0];
for(int j = 1; j < n; j++){
dp[j] = max(dp[j-1], dp[j]) + grid[i][j];
}
}
return dp[n-1];
}
剑指 Offer 48. 最长不含重复字符的子字符串
【滑动窗口(双指针)】
解:
int lengthOfLongestSubstring(string s) {
int l = 0, r = 0;
unordered_map<char, int> mp;
int maxLen = 0;
while(r < s.length()){
char c = s[r];
r++;
mp[c]++;
while(mp[c] > 1 && l < r){
char t = s[l];
l++;
mp[t]--;
}
maxLen = max(maxLen, r-l); // 在此更新,保证是没有重复元素
}
return maxLen;
}
剑指 Offer 49. 丑数
【多路归并(三指针) & 优先队列】
x
为丑数,那么2*x
,3*x
、5*x
都为丑数*2: ugly[1]*2 -> ugly[2]*2 -> ugly[3]*2 -> ugly[4]*2 ...
*3: ugly[1]*3 -> ugly[2]*3 -> ugly[3]*3 -> ugly[4]*3 ...
*5: ugly[1]*5 -> ugly[2]*5 -> ugly[3]*5 -> ugly[4]*5 ...
手动合并:
int nthUglyNumber(int n) {
vector<long> ugly(n+2, 0);
int k = 1;
int p2 = 1, p3 = 1, p5 = 1;
long product2 = 1, product3 = 1, product5 = 1;
while(k <= n){
long minn = min(product2, min(product3, product5));
ugly[k++] = minn;
if(minn == product2){
product2 = 2 * ugly[p2++];
}
if(minn == product3){
product3 = 3 * ugly[p3++];
}
if(minn == product5){
product5 = 5 * ugly[p5++];
}
}
return ugly[n];
}
优先队列(最小堆):
//上面的思路:若X为丑数,则2*x,3*x,5*x也为丑数
int nthUglyNumber(int n){
vector<long> ugly(n+2, 0);
vector<int> factor{2,3,5};
int k = 1;
priority_queue<long, vector<long>, greater<long>> q;
q.push(1);
while(k <= n){
long minn = q.top();
q.pop();
ugly[k++] = minn;
while(!q.empty() && q.top() == minn) q.pop();
for(int fac: factor){
q.push(minn * fac);
}
}
return ugly[n];
}
//手动合并的思路:三指针
int nthUglyNumber(int n){
vector<long> ugly(n+2, 0);
vector<int> factor{2, 3, 5};
priority_queue<node> q;
int k = 1;
for(int fac: factor){
q.push(node{1, fac, 1});
}
while(k <= n){
node t = q.top();
q.pop();
if(t.pro != ugly[k-1]) //1
ugly[k++] = t.pro;
q.push(node{ugly[t.id] * t.fac, t.fac, t.id+1});
// while(!q.empty() && q.top().pro == t.pro){ //2。1、2择一即可
// t = q.top();
// q.pop();
// q.push(node{ugly[t.id] * t.fac, t.fac, t.id+1});
// }
}
return ugly[n];
}
struct node{
long pro;
int fac;
int id;
friend bool operator<(const node &a, const node &b){
return a.pro > b.pro;
}
};
剑指 Offer 50. 第一个只出现一次的字符
【哈希表&有序哈希表】
解:
char firstUniqChar(string s) {
unordered_map<char,int> mp;
for(char c: s) mp[c]++;
for(char c: s){
if(mp[c] == 1) return c;
}
return ' ';
}
char firstUniqChar(string s){
vector<char> vec;
unordered_map<char, bool> mp;
for(char c: s){
if(mp.find(c) == mp.end()){ //之前没出现过,就没操作过mp[c],mp.find(c)就返回末尾
vec.push_back(c); //插入
}
mp[c] = (mp.find(c) == mp.end()); //若mp.find(c) == mp.end(),说明字符c是否之前没出现过,标记为1;若后面再出现了,mp.find(c)就不等于末尾了,赋值为0
}
for(char c: vec){
if(mp[c]) return c;
}
return ' ';
}
剑指 Offer 51. 数组中的逆序对
【归并排序,离散化+树状数组(待整理),离散化+线段树(待整理)】
第二种方法的实现:
int cnt = 0;
int reversePairs(vector<int>& nums) {
tmp.reserve(nums.size());
mergeSort(nums, 0, nums.size()-1);
return cnt;
}
vector<int> tmp; //tmp.reserve(nums.size());
void mergeSort(vector<int>&nums, int l, int r){
if(l >= r) return;
int mid = l + ((r - l) >> 1);
mergeSort(nums, l, mid);
mergeSort(nums, mid+1, r);
merge(nums, l, mid, r);
}
void merge(vector<int>&nums, int l, int mid, int r){
for(int p = l; p <= r; p++) tmp[p] = nums[p];
int i = l, j = mid+1;
for(int p = l; p <= r; p++){
if(i == mid+1) nums[p] = tmp[j++];
else if(j == r+1) nums[p] = tmp[i++];
else if(tmp[i] <= tmp[j]){
nums[p] = tmp[i++];
}
else{
cnt += (mid + 1 - i); //方法2求逆序对,只需在这多加一句。当tmp[i]>tmp[j]时,对于编号j,编号i~mid的这(mid+1-i)个数与它组成逆序对
nums[p] = tmp[j++];
}
}
}
第一种方法的实现:
int cnt = 0;
int reversePairs(vector<int>& nums) {
tmp.reserve(nums.size());
mergeSort(nums, 0, nums.size()-1);
return cnt;
}
vector<int> tmp; //tmp.reserve(nums.size());
void mergeSort(vector<int>&nums, int l, int r){
if(l >= r) return;
int mid = l + ((r - l) >> 1);
mergeSort(nums, l, mid);
mergeSort(nums, mid+1, r);
merge(nums, l, mid, r);
}
void merge(vector<int>&nums, int l, int mid, int r){
for(int p = l; p <= r; p++) tmp[p] = nums[p];
int i = l, j = mid+1;
for(int p = l; p <= r; p++){
if(i == mid+1) nums[p] = tmp[j++];
else if(j == r+1){
cnt += (r+1-(mid+1)); //方法1求逆序对,当j走完了时,对于编号i,右边区间比它小的元素有(r+1-(mid+1))个,与它组成逆序对
nums[p] = tmp[i++];
}
else if(tmp[i] <= tmp[j]){
cnt += (j - (mid+1)); //方法1求逆序对,当tmp[i]<=tmp[j]时,对于编号i,编号mid+1~j-1的这(j-1+1-(mid+1))=(j-(mid+1))个数与它组成逆序对
nums[p] = tmp[i++];
}
else{
nums[p] = tmp[j++];
}
}
}
剑指 Offer 52. 两个链表的第一个公共节点
【链表相交】
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* p1 = headA, *p2 = headB;
while(p1 != p2){
if(!p1) p1 = headB;
else p1 = p1->next;
if(!p2) p2 =headA;
else p2 = p2->next;
}
return p1;
}
剑指 Offer 53 - I. 在排序数组中查找数字 I
【二分】
解:二分
思路1:==target
的左边界l,==target
的右边界r,若找不到返回0,找到了就是r+1-l
思路2:<=target
的右边界r,<=(target-1)
的右边界l,返回r-l
(二分函数找不到返回的都是-1)
int search(vector<int>& nums, int target){
int r = binarySearch(nums, 0, nums.size(), target);
int l = binarySearch(nums, 0, r+1, target-1);
return r - l;
}
int binarySearch(vector<int> &nums, int l, int r, int target){ // 满足<=target的右边界
int ans = -1;
while(l < r){
int mid = l + ((r - l) >> 1);
if(nums[mid] <= target){
ans = mid;
l = mid+1;
}
else r = mid;
}
return ans;
}
剑指 Offer 53 - II. 0~n-1中缺失的数字
【位运算&二分】
int missingNumber(vector<int>& nums) {
int n = nums.size();
int res = 0;
for(int i = 0; i<= n; i++){
res^=i;
}
for(int num: nums) res^=num;
return res;
}
nums[i]==i
,缺失元素后面nums[i]!=i
nums[i]==i
的右边界 int missingNumber(vector<int>& nums){
int num = binarySearch(nums);
return num+1;
}
int binarySearch(vector<int>& nums){
int l = 0, r = nums.size();
int ans = -1;
while(l < r){
int mid = l + ((r - l) >> 1);
if(nums[mid] == mid){
ans = mid;
l = mid+1;
}
else r = mid;
}
return ans;
}
剑指 Offer 54. 二叉搜索树的第k大节点
【DFS,BST性质】
利用中序遍历BST得到升序序列(严谨:不递减序列)的性质。求第k大元素是倒着数的,那么使用先遍历右子树的中序遍历。
//利用BST中序遍历得到递增序列的特性
int cnt = 0, ans = 0;
int kthLargest(TreeNode* root, int k) {
dfs(root, k);
return ans;
}
void dfs(TreeNode* root, int k){
if(!root) return;
dfs(root->right, k);
cnt++;
if(cnt == k){
ans = root->val;
return;
}
else if(cnt > k) return;
dfs(root->left, k);
}
unordered_map<TreeNode*, int> siz;
int kthLargest(TreeNode* root, int k){
getSize(root);
return dfs_kth(root, k);
}
int getSize(TreeNode* root){
if(!root){
return 0;
}
siz[root] = getSize(root->left) + getSize(root->right) +1;
return siz[root];
}
int dfs_kth(TreeNode* root, int k){
if(siz[root->right] + 1 > k) return dfs_kth(root->right, k);
else if(siz[root->right] + 1 < k) return dfs_kth(root->left, k - (siz[root->right] + 1));
return root->val;
}
剑指 Offer 55 - I. 二叉树的深度
【DFS&BFS】
int maxDepth(TreeNode* root) {
return dfs(root);
}
int dfs(TreeNode* root){
if(!root) return 0;
return max(dfs(root->left), dfs(root->right)) + 1;
}
int maxx = 0;
int maxDepth(TreeNode* root) {
dfs(root, 0);
return ans;
}
void dfs(TreeNode* root, int depth){
if(!root) return;
depth++;
maxx = max(maxx, depth);
dfs(root->left, depth);
dfs(root->right, depth);
}
int maxDepth(TreeNode* root){
if(!root) return 0;
queue<TreeNode*> q;
q.push(root);
int depth = 0;
while(!q.empty()){
int n = q.size();
depth++;
for(int i = 0; i < n; i++){
TreeNode* t = q.front();
q.pop();
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
}
return depth;
}
剑指 Offer 55 - II. 平衡二叉树
【DFS】
bool flag = 1;
bool isBalanced(TreeNode* root){
getDepth(root);
return flag;
}
int getDepth(TreeNode* root){
if(!root || !flag) return 0; //子树已经不平衡了,不再继续找了,直接返回
//下面还是flag=1时
int depL = getDepth(root->left);
int depR = getDepth(root->right);
if(abs(depL - depR) > 1) flag = 0;
return max(depL, depR)+1;
}
剑指 Offer 56 - I. 数组中数字出现的次数
【位运算,异或的性质】
一个数组中除了两个数字只出现一次外,其它数字都出现了两次,找到这两个只出现一次的数字。
a^a = 0, a^0 = 0
sum = a^b
a^b
)的二进制表示
a!=b
,所以sum必然不为0,k一定存在 vector<int> singleNumbers(vector<int>& nums){
int sum = 0;
for(int num: nums) sum^=num;
int idx = lowbit(sum);
int sum_0 = 0, sum_1 = 0;
for(int num: nums){
if(num & idx){
sum_1 ^= num;
}
else sum_0 ^= num;
}
return vector<int>{sum_0, sum_1};
}
int lowbit(int x){ return x&-x;}
剑指 Offer 56 - II. 数组中数字出现的次数 II
【位运算,有限自动机(待整理)】
int singleNumber(vector<int>& nums) {
vector<int> mp(32,0); //统计数组中的数字,在32位二进制表示下,某一位为1的总个数
for(int num: nums){
int k = 0;
while(num){
mp[k] += (num & 1);
num >>= 1;
k++;
}
}
int n = 0, t = 1;
for(int i = 0; i < 32; i++){
if(mp[i] && mp[i] % 3){ //当该位为1的总个数不能整除3时,说明包含了只出现一次的那个数a,a的第i位为1
n += t; //加上2^i
}
t = (unsigned)t << 1; //用unsigned防止溢出,负数左移没有定义
}
return n;
}
剑指 Offer 57. 和为s的两个数字
【双指针】
vector<int> twoSum(vector<int>& nums, int target) {
int left = 0, right = nums.size()-1;
while(left < right){
int sum = nums[left] + nums[right];
if(sum == target){
return vector<int>{nums[left], nums[right]};
}
else if(sum > target) right--;
else left++;
}
return {};
}
剑指 Offer 57 - II. 和为s的连续正数序列
【双指针(滑动窗口)】
双指针思路:
vector<vector<int>> findContinuousSequence(int target){
int i = 1, j = 1;
int sum = 0;
vector<vector<int>> ans;
while(i <= target/2){
if(sum < target){
sum += j;
j++;
}
else if(sum > target){
sum -= i;
i++;
}
else{
vector<int> res;
for(int k = i; k < j; k++) res.push_back(k); //是左闭右开[i,j)区间
ans.push_back(res);
sum -= i;
i++;
}
}
return ans;
}
滑动窗口思路:
vector<vector<int>> findContinuousSequence(int target){
int left = 1, right = 1;
int sum = 0;
vector<vector<int>> ans;
while(left <= target/2){
sum += right;
right++;
while(sum >= target){
if(sum == target){
vector<int> res;
for(int i = left; i < right; i++) res.push_back(i); //是左闭右开[left,right)区间
ans.push_back(res);
}
sum -= left;
left++;
}
}
return ans;
}
剑指 Offer 58 - I. 翻转单词顺序
单词顺序翻转,多个空格变一个。且要求原地修改字符串
方法:
思路:移出多余空格->字符串反转->单词反转
1.移出多余空格
双指针(快慢指针),resize字符串大小
O ( n ) O(n) O(n)
思路同27.移除元素
void removeExtraSpaces(string &s){
int slow = 0, fast = 0;
while(fast < s.size()){
if(s[fast] != ' '){
if(slow) s[slow++] = ' '; //手动控制添加空格,在不是第一个的单词前添加一个空格
while(fast < s.size() && s[fast] != ' '){
s[slow++] = s[fast++];
}
}
fast++;
}
s.resize(slow); //carefull!
}
2.字符串反转
双指针
void reverse(string &s, int i, int j){
while(i < j) swap(s[i++], s[j--]);
}
3.单词反转
没有使用额外空间,所以空间复杂度为 O ( 1 ) O(1) O(1)
string reverseWords(string s) {
removeExtraSpaces(s);
reverse(s, 0, s.size()-1);
int left = 0;
for(int right = 0; right <= s.size(); right++){
if(right == s.size() || s[right] == ' '){
reverse(s, left, right-1);
left = right+1;
}
}
return s;
}
void removeExtraSpaces(string &s){
int slow = 0, fast = 0;
while(fast < s.size()){
if(s[fast] != ' '){
if(slow) s[slow++] = ' ';
while(fast < s.size() && s[fast]!= ' '){
s[slow++] = s[fast++];
}
}
fast++;
}
s.resize(slow);
}
void reverse(string &s, int i, int j){
while(i < j) swap(s[i++], s[j--]);
}
剑指 Offer 58 - II. 左旋转字符串
【切片,取余】
解:
string reverseLeftWords(string s, int n) {
int len = s.length();
n %= len;
// string t = s.substr(0, n);
// s = s.substr(n, len-n);
// s += t;
return s.substr(n, len-n) + s.substr(0, n);
}
string reverseLeftWords(string s, int n){
int len = s.length();
n %= len;
string res = "";
for(int i = n; i< n+len; i++){
res += s[i%len];
}
return res;
}
剑指 Offer 59 - I. 滑动窗口的最大值
【单调队列】
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(!nums.size()) return {};
vector<int> res;
int left = 0, right =0;
myQueue window;
while(right < nums.size()){
window.push(nums[right]);
right++;
if(right - left == k && left < nums.size()){
res.push_back(window.maxValue());
window.pop(nums[left]);
left++;
}
}
return res;
}
class myQueue{
public:
deque<int> dq;
void push(int x){
while(!dq.empty() && dq.back() < x){
dq.pop_back();
}
dq.push_back(x);
}
int maxValue(){
// if(dq.empty()) return -1;
return dq.front();
}
void pop(int x){
if(!dq.empty() && dq.front() == x) dq.pop_front();
}
};
剑指 Offer 60. n个骰子的点数
【概率DP】
n个骰子所能掷出的点数之和的所有可能性的概率。
解:
i~6i
,6i的概率必为0,所以只关注i~6i
即可// dp[i][j]:掷i个骰子,掷出的点数之和为j的概率
vector<double> dicesProbability(int n) {
vector<vector<double>> dp(n+1, vector<double>(6*n+1, 0.0)); //下标偏移1位
for(int j = 1; j<= 6;j++) dp[1][j] = 1.0/6;
for(int i = 2; i<= n; i++){
for(int j = i; j <= 6*i; j++){
for(int k = 1; k <= 6; k++){
if(j-k <= 0) break; //if(j-k <= 0 || j-k
dp[i][j] += 1.0/6*dp[i-1][j-k];
}
}
}
vector<double> res;
for(int j = n; j<= 6*n; j++) res.push_back(dp[n][j]);
return res;
}
//dp[i,j]:掷i个骰子,掷出的点数之和为j的方法数
vector<double> dicesProbability(int n) {
vector<vector<double>> dp(n+1, vector<double>(6*n+1, 0.0)); //下标偏移1位
for(int j = 1; j<= 6;j++) dp[1][j] = 1.0;
for(int i = 2; i<= n; i++){
for(int j = i; j <= 6*i; j++){
for(int k = 1; k <= 6; k++){
if(j-k <= 0) break; //if(j-k <= 0 || j-k
dp[i][j] += dp[i-1][j-k];
}
}
}
vector<double> res;
double cnt = pow(6.0, n); //n=6时状态空间总数
for(int j = n; j<= 6*n; j++) res.push_back(dp[n][j]/cnt);
return res;
}
优化:空间压缩,降维
vector<double> dicesProbability(int n) {
vector<double> dp(6*n+1, 0.0); //下标偏移1位
for(int j = 1; j<= 6;j++) dp[j] = 1.0/6;
for(int i = 2; i<= n; i++){
for(int j = 6*i; j >=i; j--){ //通过递推式(状态转移方程)得出要逆序遍历
dp[j] = 0.0;
for(int k = 1; k <= 6; k++){
if(j-k <= 0 || j-k<i-1) break; //必须大于(i-1)状态的对角线(即大于i-1)才行。因为状态压缩后,小于i-1的格子dp[k](k
dp[j] += 1.0/6*dp[j-k];
}
}
}
vector<double> res;
for(int j = n; j<= 6*n; j++) res.push_back(dp[j]);
return res;
}
剑指 Offer 62. 圆圈中最后剩下的数字
【DP,约瑟夫环】
方法1:链表模拟,
方法2:DP+数学
约瑟夫环问题:n个人排成一个圆圈做游戏,每轮游戏去掉第m个人,然后从第(m+1)个开始计数开始新一轮,直到剩余一个人。
分析:
共n个数,每轮去掉一个数,显然需要有(n-1)轮,直到最后剩余1个数为止
每轮去掉的都是第m个数,会把第(m+1)个数移到第一位
所以对于有i个数的数组,它去掉第m个数,把第(m+1)个数移到第一位后,变成了有(i-1)个数的数组
举例(为清晰,把数组中的元素写成字母)
设n=8,m=3
0 1 2 3 4 5 6 7
A B (C) D E F [G] H // n=8, id = (3+3)%8=6
D E (F) [G] H A B //n=7, id = (0+3)%7 = 3
[G] H (A) B D E //n=6, id = (3+3)%6 = 0
B D (E) [G] H //n=5, id = (0+3)%5 = 3
[G] H (B) D //n=4, id = (1+3)%4 = 0
D [G] (H) //n=3, id = (1+3) % 3 = 1
(D) [G] //n=2,id = (0+3) % 2 = 1
[G] //n=1,id = 0
递归:
int lastRemaining(int n, int m) {
if(n == 1) return 0;
return (lastRemaining(n-1, m)+m)%n;
}
递推:
int lastRemaining(int n, int m){
int pos = 0;
for(int i = 2; i <= n; i++){
pos = (pos+m) % i;
}
return pos;
}
剑指 Offer 63. 股票的最大利润
【贪心&dp】
最多只能买卖一次股票
解:
int maxProfit(vector<int>& prices){
int minPrice = INT_MAX;
int maxProfit = 0;
for(int i = 0; i< prices.size(); i++){
int profit = prices[i] - minPrice;
maxProfit = max(maxProfit, profit);
minPrice = min(minPrice, prices[i]);
}
return maxProfit;
}
剑指 Offer 64. 求1+2+…+n
利用逻辑运算符的短路效应,判断是否继续递归
int sum = 0;
int sumNums(int n) {
bool tmp = (n - 1 > 0) && sumNums(n-1);
sum += n;
return sum;
}
剑指 Offer 65. 不用加减乘除做加法
实现CPU加法器
迭代版:
int add(int a, int b) {
while(b){
int c = a & b;
a = a ^ b;
b = (unsigned)c << 1; //负数左移没有定义
}
return a;
}
递归版:
int add(int a, int b){
return b? add(a ^ b, (unsigned)(a & b) << 1): a;
}
剑指 Offer 66. 构建乘积数组
【前缀和&后缀和思想】
vector<int> constructArr(vector<int>& a) {
int n = a.size();
if(!n) return{};
vector<int> b(n, 1);
for(int i = 1; i < n; i++) b[i] = b[i-1] * a[i-1]; //维护前缀乘
int tmp = 1; //维护后缀乘
for(int i = n-2; i >= 0; i--){
tmp*= a[i+1];
b[i] *= tmp;
}
return b;
}
剑指 Offer 64. 求1+2+…+n
利用逻辑运算符的短路效应,判断是否继续递归
int sum = 0;
int sumNums(int n) {
bool tmp = (n - 1 > 0) && sumNums(n-1);
sum += n;
return sum;
}
面试题13. 机器人的运动范围
【DFS&BFS】
很标准的DFS&BFS模板
解:
int dx[4] = {-1,1,0,0};
int dy[4] = {0,0,-1,1};
int movingCount(int m, int n, int k) {
vector<vector<bool>> vis(m+2, vector<bool>(n+2, 0));
return dfs(0,0,m,n,k,vis);
}
int dfs(int i, int j, int m, int n,int k, vector<vector<bool>> &vis){
if(!isValid(i, j, m, n, k) || vis[i][j]) return 0;
vis[i][j] = 1;
int res = 1;
for(int z = 0; z < 4; z++){
int x = i + dx[z];
int y = j + dy[z];
res += dfs(x, y,m, n,k, vis);
}
return res;
}
int isValid(int x, int y, int m, int n, int k){
if(x < 0 || y < 0 || x >= m || y >= n || sumRC(x, y) > k) return 0;
return 1;
}
int sumRC(int i, int j){
int sum = 0;
sum += getSum(i);
sum += getSum(j);
return sum;
}
int getSum(int x){
int sum = 0;
while(x){
sum += x%10;
x /= 10;
}
return sum;
}
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
struct node{
int x, y;
node(int x, int y): x(x), y(y){}
};
int movingCount(int m, int n, int k){
vector<vector<bool>> vis(m+2, vector<bool>(n+2, 0));
return bfs(0, 0, m, n, k, vis);
}
int bfs(int stx, int sty, int m, int n, int k, vector<vector<bool>>& vis){
queue<node> q;
q.push(node(stx, sty));
vis[stx][sty] = 1;
int res = 0;
while(!q.empty()){
node p = q.front();
q.pop();
res++;
for(int i = 0; i < 4; i++){
int x = p.x + dx[i];
int y = p.y + dy[i];
if(isValid(x, y, m, n, k) && !vis[x][y]){
vis[x][y] = 1; //立即设定vis[x][y]=1,防止后面队列访问(x,y)之前又有点能够走到x,y,此时队列里就有重复的了。注意但dijsktra算法是允许放入重复的。
q.push(node(x, y));
}
}
}
return res;
}
int isValid(int x, int y, int m, int n, int k){
if(x < 0 || y < 0 || x >= m || y >= n || sumRC(x, y) > k) return 0;
return 1;
}
int sumRC(int x, int y){
int sum = getSum(x);
sum += getSum(y);
return sum;
}
int getSum(int x){
int sum = 0;
while(x){
sum += x%10;
x /= 10;
}
return sum;
}
面试题45. 把数组排成最小的数
【自定义排序】
自定义排序规则:return (s1+s2) < (s2+s1);
分别拼接(拼接后长度相等),小的(也即字典序小的)在前
方法1:内置sort函数
lambda表达式:
string minNumber(vector<int>& nums) {
sort(nums.begin(), nums.end(), [](int x, int y){
string t1 = to_string(x), t2 = to_string(y);
return (t1+t2) < (t2+t1);
});
string res = "";
for(int num: nums){
res += to_string(num);
}
return res;
}
自定义:
struct cmp{
bool operator()(int x, int y){
string t1 = to_string(x), t2 = to_string(y);
return (t1+t2) < (t2+t1);
}
};
string minNumber(vector<int>& nums){
sort(nums.begin(), nums.end(), cmp());
string res = "";
for(int num: nums){
res += to_string(num);
}
return res;
}
string minNumber(vector<int>& nums){
quickSort(nums, 0, nums.size()-1);
string res = "";
for(int num: nums){
res += to_string(num);
}
return res;
}
void quickSort(vector<int>& nums, int l, int r){
if(l >= r) return;
int p = partition(nums, l, r);
quickSort(nums, l, p-1);
quickSort(nums, p+1, r);
}
int partition(vector<int>& nums, int l, int r){
string pivot = to_string(nums[l]);
int i = l+1, j = r;
while(i <= j){
while(i <= r && (to_string(nums[i]) + pivot) <= (pivot+(to_string(nums[i]))) ) i++;
while(j > l && ((pivot+(to_string(nums[j])) < (to_string(nums[j]) + pivot)))) j--;
if(i > j) break;
swap(nums[i], nums[j]);
}
swap(nums[l], nums[j]);
return j;
}
string minNumber(vector<int>& nums){
tmp.reserve(nums.size());
mergeSort(nums, 0, nums.size()-1);
string res = "";
for(int num: nums){
res += to_string(num);
}
return res;
}
vector<int> tmp;
void mergeSort(vector<int>&nums, int l, int r){
if(l >= r) return;
int mid = l + ((r - l) >> 1);
mergeSort(nums, l, mid);
mergeSort(nums, mid+1, r);
merge(nums, l, mid, r);
}
void merge(vector<int>&nums, int l, int mid, int r){
for(int p = l; p <= r; p++) tmp[p] = nums[p];
int i = l, j = mid+1;
int p = l;
while(i <= mid && j <= r){
string t1 = to_string(tmp[i]), t2 = to_string(tmp[j]);
if((t1 + t2) <= (t2 + t1)) nums[p++] = tmp[i++];
else nums[p++] = tmp[j++];
}
while(j <= r) nums[p++] = tmp[j++];
while(i <= mid) nums[p++] = tmp[i++];
}
面试题59 - II. 队列的最大值
【单调队列】
queue<int> q1;
deque<int> q2;
MaxQueue() {
}
int max_value() {
return q2.empty()? -1:q2.front();
}
void push_back(int value) {
q1.push(value);
while(!q2.empty() && q2.back() < value){
q2.pop_back();
}
q2.push_back(value);
}
int pop_front() {
if(q1.empty()) return -1;
int x = q1.front();
q1.pop();
if(!q2.empty() && q2.front() == x) q2.pop_front();
return x;
}
面试题61. 扑克牌中的顺子
bool isStraight(vector<int>& nums) {
sort(nums.begin(), nums.end());
int i = 0;
while(i < nums.size() && !nums[i]) i++;
bool flag = 1;
for(int j = i+1; j < nums.size(); j++){
if(nums[j] == nums[j-1]) return 0;
i -= (nums[j] - 1 - nums[j-1]);
}
return i >= 0;
}