剑指offer练习
1. 剑指 Offer 03. 数组中重复的数字
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
解法一:
#include <iostream> #include <vector> #include <set> #include <algorithm> class Solution{ public: int findRepeatedNumber(std::vector<int>& nums){ std::set<int> d; std::pair<std::set<int>::iterator, bool> ret; int repeatNum = -1; for(const auto&i : nums){ ret = d.insert(i); if (ret.second==false){ repeatNum = i; break; } } return repeatNum; } }; int main(int argc, char* argv[]){ std::vector<int> nums={2, 3, 1, 0, 2, 5, 3}; Solution s = Solution(); int num = s.findRepeatedNumber(nums); std::cout << "find repeatNmuber: " << num << std::endl; system("pause"); return 0; }

2. 剑指 Offer 04. 二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
限制:
0 <= n <= 1000
0 <= m <= 1000
解法一
#include<iostream> #include<vector> #include<algorithm> using namespace std; class Solution{ public: bool findNumberIn2DArray(vector<vector<int>>& matrix, int target){ if(matrix.size()==0 || matrix[0].size()==0) return false; int rows_end = matrix.size(); int row = 0; int col = matrix[0].size()-1; while(row<rows_end && col>=0){ if (matrix[row][col] > target){ col--; } else if (matrix[row][col] < target){ row++; }else { return true; } }; return false; }; }; int main(int argc, char* argv[]){ Solution s; vector<vector<int>> matrix={{1, 4, 7, 11, 15}, {2, 5, 8, 12, 19}, {3, 6, 9, 16, 22}, {0, 13, 14, 17, 24}, {18, 21, 23, 26, 30}}; cout << "Target 5 found: " << s.findNumberIn2DArray(matrix, 5) << endl; cout << "Target 20 found: " << s.findNumberIn2DArray(matrix, 20) << endl; vector<vector<int>> matrix1={{-1, 3}}; cout << "Target 3 found: " << s.findNumberIn2DArray(matrix1, 3) << endl; vector<vector<int>> matrix2={}; cout << "Target 4 found: " << s.findNumberIn2DArray(matrix2, 4) << endl; system("pause"); return 0; }
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:
0 <= s 的长度 <= 10000
解法:
#include <string> #include <iostream> class Solution{ public: std::string replaceSpace(std::string s){ std::string array; for(int i=0; i<s.length();i++){ if(s[i]==' '){ array.push_back('%'); array.push_back('2'); array.push_back('0'); } else{ array.push_back(s[i]); } } return array; } }; int main(int argc, char* argv[]){ Solution sob; std::string s = "We are happy."; std::cout << "After replace space: " << sob.replaceSpace(s) << std::endl; return 0; }
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000
C++解法:
#include <iostream> #include <vector> #include <stack> struct ListNode{ int val; ListNode* next; ListNode(int x):val(x),next(NULL){}; }; class Solution{ public: // method one std::vector<int> reversePrint(ListNode* head){ std::vector<int> v; std::stack<int> s; while(head!=NULL){ s.push(head->val); head = head->next; } while(!s.empty()){ v.push_back(s.top()); s.pop(); } return v; }; // method 2 std::vector<int> reversePrint2(ListNode* head){ if(head==NULL) return {}; std::vector<int> v = reversePrint2(head->next); v.push_back(head->val); return v; }; }; int main(int argc, char* argv[]){ ListNode* head = new ListNode(1); head->next = new ListNode(3); head->next->next = new ListNode(2); Solution s; std::vector<int> v = s.reversePrint(head); for(auto c:v){ std::cout << c << ","; } std::cout << std::endl; std::vector<int> v2 = s.reversePrint2(head); for(auto c:v2){ std::cout << c << ","; } std::cout << std::endl; // delete head; return 0; }
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
限制:
0 <= 节点个数 <= 5000
c++解法
#include<iostream> #include <vector> #include <unordered_map> #include <string> struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x): val(x),left(NULL),right(NULL){}; }; class Solution{ private: std::unordered_map<int, int> index; public: TreeNode* buildTree(std::vector<int>& preorder, std::vector<int>& inorder){ int n = preorder.size(); for(int i=0; i<n; i++){ index[inorder[i]] = i; } return myBuildTree(preorder, inorder, 0, n-1, 0, n-1); }; TreeNode* myBuildTree(std::vector<int>& preorder, std::vector<int>& inorder,int preorder_left, int preorder_right, int inorder_left, int inorder_right){ if(preorder_left > preorder_right){ return nullptr; } int root_index = index[preorder[preorder_left]]; // 根节点在中序遍历中的位置 TreeNode *root = new TreeNode(preorder.at(preorder_left)); int left_tree_size = root_index - inorder_left; // 左子树长度 root->left = myBuildTree(preorder, inorder, preorder_left+1, preorder_left+left_tree_size, inorder_left, root_index-1); //递归构建左子树 root->right = myBuildTree(preorder, inorder, preorder_left+1+left_tree_size, preorder_right, root_index+1, inorder_right); //递归构建右子树 return root; } }; void preorderPrint(TreeNode* n, std::string s="root"){ if(n!=NULL){ std::cout << s <<":" << n->val <<"," << std::endl; preorderPrint(n->left, "left"); preorderPrint(n->right, "right"); } } int main(int argc, char* argv[]){ Solution s; std::vector<int> preorder= {3,9,20,15,7}; std::vector<int> inorder = {9,3,15,20,7}; // std::vector<int>& preorder = {} TreeNode* p = s.buildTree(preorder, inorder); std::cout << "After build tree" <<std::endl; preorderPrint(p); return 0; }
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
提示:
1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用
c++解法:
#include<iostream> #include<stack> class CQueue{ public: CQueue(){ while(!s1.empty()){ s1.pop(); } while(!s2.empty()){ s2.pop(); } } void appendTail(int value){ s1.push(value); } int deleteHead(){ if(s2.empty()){ while(!s1.empty()){ s2.push(s1.top()); s1.pop(); } } int headValue = -1; if(!s2.empty()){ headValue = s2.top(); s2.pop(); } return headValue; } private: std::stack<int> s1, s2; }; int main(int argc, char* argv[]){ CQueue* obj = new CQueue(); obj->appendTail(3); std::cout << obj->deleteHead() << "," << obj->deleteHead() << "," << std::endl; return 0; }
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
提示:
0 <= n <= 100
c++解法:
#include<iostream> // class Solution{ // public: // // 递归+缓存,单纯的递归会超时 // int cache[101] = {0}; // int fib(int n){ // if(n==0 || n== 1) return n; // if(cache[n]!=0) return cache[n]; // cache[n] = (fib(n-1) + fib(n-2))%1000000007; // return cache[n]; // } // }; class Solution{ public: // 采用动态规划 int fib(int n){ if(n==0 || n== 1) return n; int sum, a=0,b=1; while(n>=2){ sum = (a+b)%1000000007; a = b; b = sum; n = n-1; } return sum; } }; int main(int argc, char* argv[]){ Solution s; int n = 30; std::cout << n << ":" << s.fib(n) << std:: endl; return 0; }
8. 剑指 Offer 10- II. 青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
提示:
0 <= n <= 100
c++解法:
#include<iostream> class Solution{ public: // 和斐波那契数列一样,除了初始值f(0)的值不同 int numWays(int n){ if(n==0 || n==1) return 1; int sum, a=1, b=1; while(n>=2){ sum = (a+b)%1000000007; a = b; b = sum; n--; } return sum; } }; int main(int argc,char* argv[]){ Solution s; int n = 7; std::cout <<n << ": " << s.numWays(n) << std::endl; return 0; }
9. 剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
c++ 解法:
#include<iostream> #include<vector> class Solution{ public: //方法一:二分查找法, 每次和数组最右边的值比较 int minArray(std::vector<int>& numbers){ int low = 0, high = numbers.size()-1; while(low < high){ int pivot = low + (high - low)/2; // 防止(low+high)溢出,所以不采用pivot = (low+high)/2 if(numbers[pivot] < numbers[high]){ high = pivot; } else if (numbers[pivot] > numbers[high]) { low = pivot+1; } else if (numbers[pivot] == numbers[high]) { high = high-1; } } return numbers[low]; } //方法二:从左到右遍历,递增改趋势改变时为最小值 // int minArray(std::vector<int>& numbers){ // for(int i=1; i<numbers.size(); i++){ // if(numbers[i]<numbers[i-1]){ // return numbers[i]; // } // } // return numbers[0]; // } }; int main(int argc, char* argv[]){ Solution s; // std::vector<int> array = {3,4,5,1,2}; std::vector<int> array = {2,2,2,0,1}; std::cout << s.minArray(array) << std:: endl; return 0; }
10. 剑指 Offer 12. 矩阵中的路径
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。

示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false
提示:
1 <= board.length <= 200
1 <= board[i].length <= 200
board 和 word 仅由大小写英文字母组成
c++解法
#include<iostream> #include<vector> #include<string> using namespace std; class Solution{ public: bool exist(vector<vector<char>>& board, string word){ row = board.size(); if(row<=0 || word.empty()) return false; col = board[0].size(); if(col<=0) return false; for(int i=0; i<row; i++){ for(int j=0; j<col; j++){ if(depthFirstSearch(board,i, j, word, 0)) return true; } } return false; } private: int row, col; int direction[4][2] = {{-1,0},{1, 0}, {0, 1}, {0, -1}}; bool depthFirstSearch(vector<vector<char>>& board, int i, int j, string word, int index){ if(i<0 || i>=row || j<0 || j>=col || board[i][j]!=word[index]) return false; if(index==word.size()-1) return true; board[i][j] = '\0'; //标记访问过了,不再重复访问 for(int k=0; k<4;k++){ int temp_row = i+direction[k][0]; int temp_col = j+direction[k][1]; if(depthFirstSearch(board, temp_row, temp_col, word, index+1)) return true; } board[i][j] = word[index]; //四个方向遍历都失败后,表示此路不通,要将访问标记清楚掉 return false; } }; int main(int argc, char* argv[]){ Solution s; vector<vector<char>> board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}}; string word = "ABCCED"; // vector<vector<char>> board = {{'a','b'},{'c','d'}}; // string word = "abcd"; // vector<vector<char>> board = {{'a','a'}}; // string word = "aaa"; // vector<vector<char>> board = {{'C','A','A'},{'A','A','A'},{'B','C','D'}}; // string word = "AAB"; // vector<vector<char>> board = {{'A','B','C','E'},{'S','F','E','S'},{'A','D','E','E'}}; // string word = "ABCESEEEFS"; cout << word << ": " << s.exist(board, word) << endl; return 0; }
解题思路:
本问题是典型的矩阵搜索问题,可使用深度优先搜索(DFS)+ 剪枝 解决。
深度优先搜索: 可以理解为暴力法遍历矩阵中所有字符串可能性。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
剪枝: 在搜索中,遇到 这条路不可能和目标字符串匹配成功 的情况(例如:此矩阵元素和目标字符不同、此元素已被访问),则应立即返回,称之为 可行性剪枝 。
11. 剑指 Offer 13. 机器人的运动范围
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
提示:
1 <= n,m <= 100
0 <= k <= 20
c++解法
#include<iostream> #include<vector> #include<queue> using namespace std; // 方法一:广度优先搜索(只搜索右边,下边两个方向) class Solution{ //返回x的位数之和 int get(int x){ int res = 0; for(; x; x/=10){ res += x%10; } return res; } public: int movingCount(int m, int n, int k){ if(!k) return 1; vector<vector<int>> board(m, vector<int>(n, 0)); queue<pair<int,int>> Q; Q.push(make_pair(0,0)); board[0][0] = 1; int direction[2][2] = {{1, 0}, {0, 1}}; //向右和向下两个方向搜索 int ans = 1; while(!Q.empty()){ auto t = Q.front(); Q.pop(); for(int i=0; i<2; i++){ int tx = t.first+direction[i][0]; int ty = t.second+direction[i][1]; if(tx<0 || tx>=m || ty<0 || ty>=n || board[tx][ty] || get(tx)+get(ty)>k) continue; Q.push(make_pair(tx,ty)); board[tx][ty] = 1; ans++; } } return ans; } }; //方法二:采用递归的深度优先搜索 // class Solution{ // public: // int movingCount(int m, int n, int k){ // board.assign(m, vector<int>(n, 0)); // dfs(0, 0, m, n, k); // return sum; // } // private: // vector<vector<int>> board; // int direction[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // int index_sum, sum=1; // int dfs(int i, int j, int m, int n, int k){ // index_sum = i/10 + i%10 + j/10 + j%10; // if(i<0 || i>=m || j<0 || j>=n || board[i][j]==1 || index_sum > k) return 0; // cout << i << "," << j << "," <<m<< "," <<n << "," << index_sum << endl; // board[i][j]=1; // for(int c=0; c<4; c++){ // sum += dfs(i+direction[c][0], j+direction[c][1], m, n, k); // } // return 1; // } // }; int main(int argc, char* argv[]){ Solution s; // int m=2, n=3, k=1; // int m=3, n=1, k=0; int m=16, n=8, k=4; cout << s.movingCount(m, n, k)<< endl; return 0; }
解题思路:
广度优先搜索(用队列)或者深度优先搜索(用栈)
这道题还有一个隐藏的优化:我们在搜索的过程中搜索方向可以缩减为向右和向下,而不必再向上和向左进行搜索
12. 剑指 Offer 14- I. 剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
解题思路:
动态规划:
参考:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/jian-zhi-offer-14-i-jian-sheng-zi-huan-s-xopj/

贪心算法:
参考:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/mian-shi-ti-14-i-jian-sheng-zi-tan-xin-si-xiang-by/

c++解法
#include<iostream> #include<algorithm> #include<string.h> using namespace std; // 动态规划 class Solution{ public: int cuttingRope(int n){ int dp[n+1]; memset(dp, 0, sizeof(dp)); //初始化为0 dp[2] =1; for(int i=3; i<n+1; i++){ for(int j=2; j<i; j++){ dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j])); } } return dp[n]; } }; //贪心算法,尽可能的剪成长度为3的绳子 // class Solution{ // public: // int cuttingRope(int n){ // if(n<4) return n-1; // int res = 1; // while(n >= 3){ // res *=3; // n -= 3; // } // if(n==2) res *=2; // if(n==1) res = 4*res/3; // return res; // } // }; int main(int argc, char* argv[]){ Solution s; // int n = 2; int n = 10; cout << s.cuttingRope(n) << endl; }
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 1000
解题思路:此题与 面试题14- I. 剪绳子 主体等价,唯一不同在于本题目涉及 “大数越界情况下的求余问题” 。


c++解法:
#include<iostream> #include<math.h> using namespace std; class Solution{ public: int cuttingRope(int n){ if(n < 4) return n-1; int a = n/3-1; int b = n%3; int p = 1000000007; long x = 3; //防止x*x溢出 long res = 1; //防止最后res溢出 while(a>0){ if(a%2) res = (res*x)%p; //奇数,会一直是奇数 x =(x*x)%p; a /= 2; } if(b==0) res=(res*3)%p; //被3整除,最后一段为3 if(b==1) res=(res*4)%p; //余数为1,最后一段剪为2*2 if(b==2) res=(res*3*2)%p; //余数为2,最后一段剪为3*2 return res; } }; int main(int argc, char* argv[]){ Solution s; // int n = 10; // 36 int n = 56; // 774840978 cout << s.cuttingRope(n) << endl; }
14. 剑指 Offer 15. 二进制中1的个数
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
提示:
输入必须是长度为 32 的 二进制串 。
c++解法:
#include<iostream> using namespace std; //方法一 // class Solution{ // public: // int hammingWeight(uint32_t n){ // int res = 0; // while(n>0){ // if(n%2) res+=1; // n /= 2; // } // return res; // } // }; //方法二 // class Solution { // public: // int hammingWeight(uint32_t n) { // int ret = 0; // for (int i = 0; i < 32; i++) { // if (n & (1 << i)) { // ret++; // } // } // return ret; // } // }; //方法三 class Solution{ public: int hammingWeight(uint32_t n){ int res = 0; while(n>0){ if(1&n) res++; n >>= 1; } return res; } }; int main(int argc, char* argv[]){ Solution s; // uint32_t n=9; uint32_t n=11; cout << s.hammingWeight(n) <<endl; return 0; }
15. 剑指 Offer 16. 数值的整数次方
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3
输出:9.26100
示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
提示:
-100.0 < x < 100.0
-231 <= n <= 231-1
-104 <= xn <= 104
解题思路:https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/solution/mian-shi-ti-16-shu-zhi-de-zheng-shu-ci-fang-kuai-s/

c++解法
#include<iostream> #include<cmath> using namespace std; //快速幂(二分法角度) class Solution{ public: double myPow(double x, int n){ if(x==0) return 0; double res = 1; unsigned long un; //负数转化为正数 if(n<0){ x = 1/x; un = -(long)n; //-2147483648转换成正数是会溢出,所以先转换 }else{ un = n; } while(un){ if(un&1) res*=x; //奇数,n&1相当于n%2, x *= x; un >>= 1; // 相当于 n /= 2 } return res; } }; // 快速幂:递归 // class Solution{ // public: // double myPow(double x, int n){ // if(n==0) return 1; // if(n>0) // return unsignedMyPow(x, n); // else{ // return 1.0/unsignedMyPow(x, -(long)n); // } // } // double unsignedMyPow(double x, unsigned int n){ // if(n==1) return x; // double num = unsignedMyPow(x, n/2); // if(n%2){ // return num*num*x; // } // else{ // return num*num; // }; // } // }; int main(int argc, char* argv[]){ Solution s; // cout << s.myPow(2.0, 10) << endl; // cout << s.myPow(2.1, 3) << endl; // cout << s.myPow(2.0, -2) << endl; // cout << s.myPow(8.95371, -1) << endl; // cout << s.myPow(34.00515, -3) << endl; // cout << s.myPow(0.00001,2147483647) << endl; cout << s.myPow(1.00000,-2147483648) << endl; // cout << s.myPow(1.00000,-5) << endl; return 0; }
16. 剑指 Offer 17. 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
说明:
用返回一个整数列表来代替打印
n 为正整数
解题思路:由于此题返回的是int32,所以不考虑大数打印时的溢出问题,比较简单
#include<iostream> #include<vector> using namespace std; class Solution{ public: vector<int> printNumbers(int n){ int max = 1; while(n>0){ max *= 10; n -= 1; } for(int i=1; i<max; i++){ ret.push_back(i); } return ret; } private: vector<int> ret; }; int main(int argc, char* argv[]){ vector<int> ret; Solution s; // ret = s.printNumbers(1); ret = s.printNumbers(3); for(int i=0; i<ret.size(); i++){ cout << ret.at(i) <<","; } cout << endl; }
题目升级:如果题目是要求打印,没有说明是整数,则要考虑大数打印时的溢出问题,这时可以考虑用字符串:

c++解法
#include<iostream> #include<vector> #include<string> using namespace std; class Solution { public: vector<string> ans; int pos = 0; vector<string> printNumbers(int n) { string s = "0123456789"; string str = ""; dfs(s, str, n); return ans; } void dfs(string &s, string &str, int k){ if(str.length()== k){ if(pos==0){pos=1;return;} //前导零的去除 if(str.at(0)=='0') ans.push_back(str.substr(1)); //去除“01”为“1” else ans.push_back(str); // ans.push_back(atoi(str.c_str())); return ; } for(int i=0; i<s.length();++i){ str+=s[i]; dfs(s, str, k); str.pop_back(); } } }; int main(int argc, char* argv[]){ vector<string> ret; Solution s; // ret = s.printNumbers(1); ret = s.printNumbers(2); for(int i=0; i<ret.size(); i++){ cout << ret.at(i) <<","; } cout << endl; }
17. 剑指 Offer 18. 删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点
c++解法:
#include<iostream> using namespace std; struct ListNode{ int val; ListNode* next; ListNode(int x):val(x),next(NULL){} }; ListNode* initList(int nums[], int length){ ListNode* head = new ListNode(nums[0]); ListNode* prev=head; for(int i=1; i<length; i++){ prev->next = new ListNode(nums[i]); prev = prev->next; } return head; }; void printList(ListNode* head){ ListNode* prev = head; while(prev){ cout << prev->val <<","; prev = prev->next; } cout << endl; } class Solution{ public: ListNode* deleteNode(ListNode* head, int val){ if(head->val==val) return head->next; ListNode* prev = head; while(prev->next){ if(prev->next->val==val){ prev->next = prev->next->next; } else{ prev = prev->next; } } return head; } }; int main(int arc, char* argv[]){ Solution s; ListNode* head; // int nums[4] = {4, 5, 1,9}; // head = initList(nums, 4); // head = s.deleteNode(head, 1); int nums[3] = {-3, 5, -99}; head = initList(nums, 3); printList(head); head = s.deleteNode(head, -3); printList(head); return 0; }
18. 剑指 Offer 19. 正则表达式匹配
请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
提示:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。
c++解法
#include<iostream> #include<string> #include<vector> using namespace std; class Solution{ public: bool isMatch(string s, string p){ int m = s.size()+1, n=p.size()+1; vector<vector<bool>> dp(m, vector<bool>(n, false)); dp[0][0] = true; for(int j=2; j<n; j++){ dp[0][j] = dp[0][j-2] && p[j-1]=='*'; } for(int i=1; i<m; i++){ for(int j=1; j<n; j++){ if(p[j-1]=='*'){ if(dp[i][j-2]){ dp[i][j]=true; }else if(dp[i-1][j] && (s[i-1]==p[j-2] || p[j-2]=='.')){ dp[i][j]=true; } }else{ dp[i][j] = dp[i-1][j-1] && (s[i-1]==p[j-1] || p[j-1]=='.'); } } } return dp[m-1][n-1]; } }; int main(int argc, char* argv[]){ Solution s; cout << s.isMatch("aa", "a") << endl; cout << s.isMatch("aa", "a*") << endl; cout << s.isMatch("ab", ".*") << endl; cout << s.isMatch("aab", "c*a*b") << endl; cout << s.isMatch("mississippi", "mis*is*p*") << endl; return 0; }
解题思路:动态规划

19. 剑指 Offer 20. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
1. 若干空格
2. 一个 小数 或者 整数
3. (可选)一个 'e' 或 'E' ,后面跟着一个 整数
4. 若干空格
小数(按顺序)可以分成以下几个部分:
1. (可选)一个符号字符('+' 或 '-')
2. 下述格式之一:
1. 至少一位数字,后面跟着一个点 '.'
2. 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
3. 一个点 '.' ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
1. (可选)一个符号字符('+' 或 '-')
2. 至少一位数字
部分数值列举如下:
["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]
部分非数值列举如下:
["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4"]
示例 1:
输入:s = "0"
输出:true
示例 2:
输入:s = "e"
输出:false
示例 3:
输入:s = "."
输出:false
示例 4:
输入:s = " .1 "
输出:true
提示:
1 <= s.length <= 20
s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,空格 ' ' 或者点 '.' 。
c++解法:
#include<iostream> #include<string> #include<unordered_map> #include<vector> using namespace std; // 有限状态自动机(状态机) class Solution{ public: typedef unordered_map<char, int> unmap; bool isNumber(string s){ vector<unmap> states = { {{' ', 0}, {'s', 1}, {'d', 2}, {'.',4}}, // 状态0:start with 'blank' {{'d', 2}, {'.', 4}}, // 状态1: 'sign' before 'e' {{'d', 2}, {'.', 3}, {'e', 5}, {' ', 8}}, // 状态2:'digit' before 'dot' {{'d', 3}, {'e', 5}, {' ', 8}}, // 状态3:'digit' after 'dot' {{'d', 3}}, // 状态4:'digit' after 'dot' (‘blank’ before 'dot') {{'s', 6}, {'d', 7}}, // 状态5:'e' {{'d', 7}}, // 状态6:'sign' after 'e' {{'d', 7}, {' ', 8}}, // 状态7:'digit' after 'e' {{' ', 8}}, // 状态8:end with 'blank' }; // cout << states[0]['s'] << endl; int p=0; char t; for(int i=0; i<s.size(); i++){ if(s[i]>='0' && s[i]<='9') t='d'; // 数字 else if(s[i]==' ' || s[i]=='.') t=s[i]; // 小数点或空格 else if(s[i]=='+' || s[i]=='-') t='s'; // 正负号 else if(s[i]=='e' || s[i]=='E') t='e'; // e或者E,科学计数符号 else t='?'; // 非法字符 if(states[p].count(t)==0) return false; // 不存在转移路径 p = (int)states[p][t]; // 状态转移 } return p==2 || p==3 ||p==7 || p==8; // 合法的结束状态 } }; int main(int argc, char* argv[]){ Solution s; string str = "+100"; cout << s.isNumber(str) << endl; cout << s.isNumber("0") << endl; cout << s.isNumber("e") << endl; cout << s.isNumber(".") << endl; cout << s.isNumber(" .1 ") << endl; return 0; }
解题思路:有限状态自动机

20. 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
提示:
0 <= nums.length <= 50000
1 <= nums[i] <= 10000
c++解法:
#include<iostream> #include<vector> using namespace std; //首尾双指针 class Solution{ public: vector<int> exchange(vector<int>& nums){ int j = nums.size()-1; if(j<=1) return nums; int i = 0; while(i<j){ if((nums[i]&1)){ i++; continue; } if(!(nums[j]&1)){ j--; continue; } int temp = nums[j]; nums[j] = nums[i]; nums[i] = temp; // swap(nums[i], nums[j]); //耗时 } return nums; } }; // //快慢双指针 // class Solution{ // public: // vector<int> exchange(vector<int>& nums){ // int low=0, fast=0; // while(fast<nums.size()){ // if(nums[fast]&1){ // int temp = nums[low]; // nums[low] = nums[fast]; // nums[fast] = temp; // low++; // } // fast++; // } // return nums; // } // }; int main(int argc, char* argv[]){ Solution s; // vector<int> nums={1,2,3,4}; vector<int> nums={1,2,3,4,5}; // vector<int> nums={2,16,3,5,13,1,16,1,12,18,11,8,11,11,5,1}; vector<int> numsExcahneg = s.exchange(nums); for(int i=0; i<numsExcahneg.size(); i++){ cout <<numsExcahneg[i] << ","; } cout << endl; return 0; }
解题思路:
1. 首尾双指针 定义头指针 leftleft ,尾指针 rightright . leftleft 一直往右移,直到它指向的值为偶数 rightright 一直往左移, 直到它指向的值为奇数 交换 nums[left]nums[left] 和 nums[right]nums[right] . 重复上述操作,直到 left == rightleft==right . 2. 快慢双指针 定义快慢双指针 fastfast 和 lowlow ,fastfast 在前, lowlow 在后 . fastfast 的作用是向前搜索奇数位置,lowlow 的作用是指向下一个奇数应当存放的位置 fastfast 向前移动,当它搜索到奇数时,将它和 nums[low]nums[low] 交换,此时 lowlow 向前移动一个位置 . 重复上述操作,直到 fastfast 指向数组末尾 .
21. 剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
c++解法:
#include<iostream> using namespace std; struct ListNode{ int val; ListNode* next; ListNode(int x):val(x),next(NULL){}; }; //直接遍历 // class Solution{ // public: // ListNode* getKthFromEnd(ListNode* head, int k){ // int n = 0; // ListNode* temp = head; // while(temp){ // temp = temp->next; // n++; // } // while(n>k){ // head = head->next; // n--; // } // return head; // } // }; //快慢双指针 class Solution{ public: ListNode* getKthFromEnd(ListNode* head, int k){ if(head==NULL || k==0) return NULL; int n = 0; ListNode* fast = head, *slow=head; while(fast){ fast = fast->next; if(n++>=k) slow = slow->next; } if(n<k) return NULL; //异常情况,K大于链表长度 return slow; } }; //通过模板,才能使用数组引用传参 template<typename T, int N> ListNode* initList(T (&nums)[N]){ ListNode* head = new ListNode(nums[0]); ListNode* temp = head; for(int i=1; i<N; i++){ temp->next = new ListNode(nums[i]); temp = temp->next; } return head; } void printList(ListNode* head){ while(head){ cout << head->val <<","; head = head->next; } cout << endl; } int main(int argc, char* argv[]){ int nums[5] = {1, 2, 3, 4, 5}; ListNode* head = initList(nums); printList(head); Solution s; ListNode* head2 = s.getKthFromEnd(head, 3); printList(head2); }
解题思路:
方法一(直接遍历):先遍历统计链表长度,记为 n,设置一个指针走 (n-k)步,即可找到链表倒数第 k个节点。
方法二(双指针):不需要知道链表长度,指针1先走k-1步,然后指针2和指针1同时前进,当指针1指向链表最后一个元素时,指针2即为所求
22. 剑指 Offer 24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
限制:
0 <= 节点个数 <= 5000
c++解法:
#include<iostream> using namespace std; struct ListNode{ int val; ListNode* next; ListNode(int x): val(x), next(NULL){} }; class Solution{ public: ListNode* reverseList(ListNode* head){ if(head==NULL) return head; ListNode* p1=head, *p2=head->next; while(p2){ ListNode* temp = p2->next; p2->next = p1; p1 = p2; p2 = temp; } head->next = NULL; return p1; } }; //通过模板,才能使用数组引用传参 template<typename T, int N> ListNode* initList(T (&nums)[N]){ ListNode* head = new ListNode(nums[0]); ListNode* temp = head; for(int i=1; i<N; i++){ temp->next = new ListNode(nums[i]); temp = temp->next; } return head; } void printList(ListNode* head){ while(head){ cout << head->val <<","; head = head->next; } cout << endl; } int main(int argc, char* argv[]){ int nums[5] = {1, 2, 3, 4, 5}; ListNode* head = initList(nums); printList(head); Solution s; ListNode* head2 = s.reverseList(head); printList(head2); }
解题思路:
1. 前后双指针遍历 在遍历链表时,逐个翻转链表指针 2. 递归
23. 剑指 Offer 25. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000
c++解法:
#include<iostream> using namespace std; struct ListNode{ int val; ListNode* next; ListNode(int x):val(x), next(NULL){} }; class Solution{ public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2){ // if(l1==NULL) return l2; // if(l2==NULL) return l1; ListNode* p = new ListNode(0); //引入伪头节点 ListNode* temp=p; while(l1&&l2){ if(l1->val<=l2->val){ temp->next=l1; l1 = l1->next; } else if (l1->val>l2->val) { temp->next = l2; l2 = l2->next; } temp = temp->next; } temp->next = l1 ? l1: l2; // if(l1) temp->next = l1; // if(l2) temp->next = l2; return p->next; } }; // //通过模板,才能使用数组引用传参 template<typename T, int N> ListNode* initList(T (&nums)[N]){ ListNode* head = new ListNode(nums[0]); ListNode* temp = head; for(int i=1; i<N; i++){ temp->next = new ListNode(nums[i]); temp = temp->next; } return head; } void printList(ListNode* head){ while(head){ cout << head->val <<","; head = head->next; } cout << endl; } int main(int argc, char* argv[]){ int nums1[3] = {1, 2, 4}; int nums2[3] = {1, 3, 4}; ListNode* l1 = initList(nums1); ListNode* l2 = initList(nums2); printList(l1); printList(l2); Solution s; ListNode* l = s.mergeTwoLists(l1, l2); printList(l); return 0; }
24. 剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:
0 <= 节点个数 <= 10000
#include<iostream> #include<deque> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x),left(NULL),right(NULL){} }; //前序遍历二叉树,同时判断是否是子树 class Solution{ public: bool isSubStructure(TreeNode* A, TreeNode* B){ if(A==NULL or B==NULL) return false; return recur(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B); } bool recur(TreeNode* A, TreeNode* B){ if(B==NULL) return true; if((A==NULL) || (A->val!=B->val)) return false; return recur(A->left, B->left) && recur(A->right, B->right); } }; template<typename T, int N> TreeNode* initTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); deque<TreeNode*> d; d.push_back(head); int i=1; while((!d.empty()) && (i<N)){ TreeNode* temp = d.front(); d.pop_front(); temp->left = new TreeNode(nums[i]); d.push_back(temp->left); if(i+1<N){ temp->right = new TreeNode(nums[i+1]); d.push_back(temp->right); i++; } i++; } return head; } void printTree(TreeNode* node){ if(node==NULL) return; cout << node->val << ","; printTree(node->left); printTree(node->right); } int main(int argc, char* argv[]){ int nums1[3] = {1, 2, 3}; // int nums1[5] = {3,4,5,1,2}; // int nums1[9] = {4,2,3,4,5,6,7,8,9}; TreeNode* A = initTree(nums1); printTree(A); cout <<endl; int nums2[2] = {3, 1}; // int nums2[2] = {4, 1}; // int nums2[3] = {4,8,9}; TreeNode* B = initTree(nums2); printTree(B); cout << endl; Solution s; cout << s.isSubStructure(A, B); return 0; }
解题思路:前序遍历二叉树,同时对每个节点比较是否包含子树
25. 剑指 Offer 27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
限制:
0 <= 节点个数 <= 1000
c++解法:
#include<iostream> #include<deque> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x), left(NULL),right(NULL){} }; class Solution{ public: TreeNode* mirrorTree(TreeNode* root){ TreeNode* temp = root; helper(temp); return root; } void helper(TreeNode* root){ if(!root) return; TreeNode* temp = root->left; root->left = root->right; root->right = temp; helper(root->left); helper(root->right); } }; template<typename T, int N> TreeNode* initTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); deque<TreeNode*> d; d.push_back(head); int i=1; while((!d.empty()) && i<N){ TreeNode* temp = d.front(); d.pop_front(); temp->left = new TreeNode(nums[i]); d.push_back(temp->left); if(i+1<N){ temp->right = new TreeNode(nums[i+1]); d.push_back(temp->right); i++; } i++; } return head; } //前序遍历 void printTree(TreeNode* head){ if(head==NULL) return; cout << head->val<<","; printTree(head->left); printTree(head->right); } //层次遍历 void descendingOrder(TreeNode* head){ deque<TreeNode*> d; d.push_back(head); while(!d.empty()){ TreeNode* temp = d.front(); cout << temp->val << ","; if(temp->left) d.push_back(temp->left); if(temp->right) d.push_back(temp->right); d.pop_front(); } cout << endl; } int main(int argc, char* argv[]){ int nums[7] = {4,2,7,1,3,6,9}; TreeNode* head = initTree(nums); // printTree(head); descendingOrder(head); cout << endl; Solution s; TreeNode* head2 = s.mirrorTree(head); // printTree(head2); descendingOrder(head); return 0; }
26. 剑指 Offer 28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
限制:
0 <= 节点个数 <= 1000
c++ 解法:
#include<iostream> #include<deque> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x),left(NULL),right(NULL){} }; class Solution{ public: bool isSynnetrci(TreeNode* root){ if(root==NULL) return true; return helper(root->left, root->right); } bool helper(TreeNode* left, TreeNode* right){ if(left==NULL && right==NULL) return true; if((!left) || (!right)) return false; //一个结点为NULL,一个不是 return helper(left->left, right->right) && (left->val==right->val) && helper(left->right, right->left); } }; template<typename T, int N> TreeNode* initTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); deque<TreeNode*> d; d.push_back(head); int i=1; while((!d.empty()) && i<N){ TreeNode* temp = d.front(); d.pop_front(); if(nums[i]=='\0'){ temp->left=NULL; }else{ temp->left = new TreeNode(nums[i]); d.push_back(temp->left); } if(i+1<N){ if(nums[i+1]=='\0'){ temp->right=NULL; }else{ temp->right = new TreeNode(nums[i+1]); d.push_back(temp->right); } i++; } i++; } return head; } //前序遍历 void printTree(TreeNode* head){ if(head==NULL) return; cout << head->val<<","; printTree(head->left); printTree(head->right); } //层次遍历 void descendingOrder(TreeNode* head){ deque<TreeNode*> d; d.push_back(head); while(!d.empty()){ TreeNode* temp = d.front(); cout << temp->val << ","; if(temp->left) d.push_back(temp->left); if(temp->right) d.push_back(temp->right); d.pop_front(); } cout << endl; } int main(int argc, char* argv[]){ // int nums[7] = {1,2,2,3,4,4,3}; int nums[7] = {1,2,2,'\0',3,'\0',3}; // '\0'表示null,即树的NULL结点 TreeNode* head = initTree(nums); descendingOrder(head); Solution s; cout << s.isSynnetrci(head) << endl; return 0; }
解题思路:(对左子树进行中序遍历(left-root-right),对右子树进行反向中序遍历(right-root-left))
递归判断left子树和right子树: 1. left的左子树和riht的右子树相等 2. left->val==right->val 3. left的右子树和right的左子树相等
27. 剑指 Offer 29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
限制:
0 <= matrix.length <= 100
0 <= matrix[i].length <= 100
c++解法:
#include<iostream> #include<vector> using namespace std; class Solution{ public: vector<int> spiralOrder(vector<vector<int>>& matrix){ if(matrix.size()==0 || matrix[0].size()==0) return {}; vector<int> ret; int rows = matrix.size()-1; int cols = matrix[0].size()-1; int i=0, j=0; while(i<=rows && j<=cols){ for(int n=j; n<=cols;n++){ ret.push_back(matrix[i][n]); } for(int m=i+1; m<=rows;m++){ ret.push_back(matrix[m][cols]); } if(i<rows && j <cols){ for(int n=cols-1; n>=j; n--){ ret.push_back(matrix[rows][n]); } for(int m=rows-1; m>i; m--){ ret.push_back(matrix[m][j]); } } i++; j++; rows--; cols--; } return ret; } }; int main(int argc, char* argv[]){ // vector<vector<int>> matrix = {{1,2,3},{4,5,6},{7,8,9}}; vector<vector<int>> matrix = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; // vector<vector<int>> matrix = {{1,2,3,4}}; // vector<vector<int>> matrix = {{1},{2},{3},{4}}; Solution s; vector<int> ret = s.spiralOrder(matrix); for(int i=0; i<ret.size(); i++){ cout << ret.at(i)<< ","; } cout << endl; return 0; }
解体思路:

28. 剑指 Offer 30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
提示:
各函数的调用总次数不超过 20000 次
c++解法
#include<iostream> #include<vector> using namespace std; class MinStack{ public: MinStack(){ } void push(int x){ stack.push_back(x); if(_min.empty() || x<=_min.back()) _min.push_back(x); //注意这里的等号,防止重复元素pop()时出问题 } void pop(){ if(stack.back()==_min.back()) _min.pop_back(); stack.pop_back(); } int top(){ return stack.back(); } int min(){ return _min.back(); } private: vector<int> stack; vector<int> _min; }; int main(int argc, char* argv[]){ MinStack* minStack = new MinStack(); minStack->push(-2); minStack->push(0); minStack->push(-3); cout << minStack->min() << endl; minStack->pop(); cout << minStack->top() << endl; cout << minStack->min() << endl; return 0; }
解体思路:

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
提示:
0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列。
c++解法:
#include<iostream> #include<vector> using namespace std; //用一个辅助栈,模拟入栈出栈 class Solution{ public: bool validateStackSequences(vector<int>& pushed, vector<int>& popped){ int l1 = pushed.size(); int l2 = popped.size(); if(l1!=l2) return false; // 处理异常情况 int i=0, j=0; vector<int> stack; for(int i=0; i<l1; i++){ stack.push_back(pushed[i]); while((!stack.empty()) && stack.back()==popped[j]){ j++; stack.pop_back(); } } return stack.empty(); } }; // class Solution{ // public: // bool validateStackSequences(vector<int>& pushed, vector<int>& popped){ // int l1 = pushed.size(); // int l2 = popped.size(); // if(l1!=l2) return false; // 处理异常情况 // int i=0, j=0; // vector<int> stack; // while(j<l2){ // if(stack.empty() || stack.back()!=popped[j]){ // stack.push_back(pushed[i++]); // }else{ // stack.pop_back(); // j++; // } // if(i==l1 && (!stack.empty()) && stack.back()!=popped[j]) return false; // } // return true; // } // }; int main(int argc, char* argv[]){ vector<int> pushde = {1,2,3,4,5}; // vector<int> popped = {4,5,3,2,1}; vector<int> popped = {4,3,5,1,2}; Solution s; cout << s.validateStackSequences(pushde, popped) << endl; return 0; }
解题思路: 考虑借用一个辅助栈 stackstack ,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果。 入栈操作: 按照压栈序列的顺序执行。 出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。
30. 剑指 Offer 32 - I. 从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
提示:
节点总数 <= 1000
c++解法:
#include<iostream> #include<vector> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode *left; TreeNode* right; TreeNode(int x):val(x),left(NULL),right(NULL){} }; class Solution{ public: vector<int> levelOrder(TreeNode* root){ if(!root) return {}; vector<int> level; queue<TreeNode*> q; q.push(root); while(!q.empty()){ TreeNode* temp = q.front(); q.pop(); level.push_back(temp->val); if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); } return level; } }; //创建树 template<typename T, int N> TreeNode* initTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); queue<TreeNode*> d; d.push(head); int i=1; while((!d.empty()) && i<N){ TreeNode* temp = d.front(); d.pop(); if(nums[i]=='\0'){ temp->left=NULL; }else{ temp->left = new TreeNode(nums[i]); d.push(temp->left); } if(i+1<N){ if(nums[i+1]=='\0'){ temp->right=NULL; }else{ temp->right = new TreeNode(nums[i+1]); d.push(temp->right); } i++; } i++; } return head; } int main(int argc, char*argv[]){ int nums[7] = {3,9,20,'\0','\0',15,7}; TreeNode* head = initTree(nums); Solution s; vector<int> ret = s.levelOrder(head); for(int i=0; i<ret.size(); i++){ cout << ret[i] << ","; } cout << endl; return 0; }
解题思路:二叉树的广度优先搜索(BFS,采用队列实现)


31. 剑指 Offer 32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
提示:
节点总数 <= 1000
c++解法:
#include<iostream> #include<vector> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x),left(NULL), right(NULL){} }; class Solution{ public: vector<vector<int>> levelOrder(TreeNode* root){ if(!root) return {}; vector<vector<int>> level; vector<int> vt; queue<TreeNode*> q; q.push(root); while(!q.empty()){ vt.clear(); int s = q.size(); // 记住每一层的结点个数 for(int i=0; i<s;i++){ TreeNode* temp = q.front(); q.pop(); vt.push_back(temp->val); if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); } if(!vt.empty()) level.push_back(vt); } return level; } }; //创建树 template<typename T, int N> TreeNode* initTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); queue<TreeNode*> d; d.push(head); int i=1; while((!d.empty()) && i<N){ TreeNode* temp = d.front(); d.pop(); if(nums[i]=='\0'){ temp->left=NULL; }else{ temp->left = new TreeNode(nums[i]); d.push(temp->left); } if(i+1<N){ if(nums[i+1]=='\0'){ temp->right=NULL; }else{ temp->right = new TreeNode(nums[i+1]); d.push(temp->right); } i++; } i++; } return head; } int main(int argc, char*argv[]){ int nums[7] = {3,9,20,'\0','\0',15,7}; TreeNode* head = initTree(nums); Solution s; vector<vector<int>> ret = s.levelOrder(head); for(int i=0; i<ret.size(); i++){ vector<int> vret = ret[i]; for(int j=0; j<vret.size(); j++){ cout << vret[j] << ","; } cout << endl; } return 0; }
解题思路: 和上一题一样,需要记住每层的结点个数
32. 剑指 Offer 32 - III. 从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
提示:
节点总数 <= 1000
c++解法:
#include<iostream> #include<vector> #include<deque> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x),left(NULL),right(NULL){} }; //采用队列queue,列表添加val时逆序添加 class Solution{ public: vector<vector<int>> levelOrder(TreeNode* root){ if(!root) return {}; vector<vector<int>> level; vector<int> ve; queue<TreeNode*> q; q.push(root); bool even = false; while(!q.empty()){ int s = q.size(); ve.assign(s, 0); for(int i=0; i<s; i++){ TreeNode* temp = q.front(); q.pop(); if(even){ ve[s-1-i] = temp->val; }else{ ve[i] = temp->val; } if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); } if(!ve.empty()) level.push_back(ve); even = !even; } return level; } }; //使用双端队列deque // class Solution{ // public: // vector<vector<int>> levelOrder(TreeNode* root){ // if(!root) return {}; // vector<vector<int>> level; // vector<int> ve; // deque<TreeNode*> q; // q.push_back(root); // bool even = false; // while(!q.empty()){ // int s = q.size(); // ve.clear(); // for(int i=0; i<s; i++){ // TreeNode* temp; // if(!even){ // temp = q.back(); // q.pop_back(); // if(temp->left) q.push_front(temp->left); // if(temp->right) q.push_front(temp->right); // }else{ // temp = q.front(); // q.pop_front(); // if(temp->right) q.push_back(temp->right); // if(temp->left) q.push_back(temp->left); // } // ve.push_back(temp->val); // } // if(!ve.empty()) level.push_back(ve); // even = !even; // } // return level; // } // }; template<typename T, int N> TreeNode* initTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); deque<TreeNode*> q; q.push_back(head); int i = 1; while(!q.empty() && i<N){ TreeNode* temp = q.front(); q.pop_front(); if(nums[i]!='\0'){ temp->left = new TreeNode(nums[i]); q.push_back(temp->left); } if(i+1<N && nums[i+1]!='\0'){ temp->right = new TreeNode(nums[i+1]); q.push_back(temp->right); } i += 2; } return head; } int main(int argc, char* argv[]){ // int nums[] = {3,9,20,'\0','\0',15,7}; // int nums[] = {1,2,3,4,'\0','\0',5}; int nums[] = {1,2,3,4,6,7,5}; TreeNode* head = initTree(nums); Solution s; vector<vector<int>> ret = s.levelOrder(head); for(int i=0; i<ret.size(); i++){ for(int j=0; j<ret[i].size(); j++){ cout << ret[i][j] << ","; } cout << endl; } return 0; }
解题思路:
方法一:还是采用队列,偶数层添加val时,倒序添加
方法二:采用双端队列,偶数层倒序遍历结点
33. 剑指 Offer 33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
提示:
数组长度 <= 1000
c++解法:
#include<iostream> #include<vector> using namespace std; // 二叉搜索树BST:对于所有子树满足:左结点小于父节点,父节点小于右结点;(因此,左子树所有结点小于根节点,右子树所有结点大于根节点) //二叉搜索树BST后续遍历数组特点:数组最后一个元素一定是根节点,数组左半部分为根节点的左子树,右半部分为根节点的右子树;且左半部分结点小于根节点,右半部分节点的值大于根节点 class Solution{ public: bool verifyPostorder(vector<int>& postorder){ int s = postorder.size(); return recurBST(0, s-1,postorder); } bool recurBST(int i, int j, vector<int>& postorder){ if(i>=j) return true; int p=i; while(postorder[p]<postorder[j]) p++; int m = p; while(postorder[p]>postorder[j]) p++; return p==j && recurBST(i,m-1,postorder) && recurBST(m, j-1,postorder); } }; int main(int argc, char* argv[]){ // vector<int> p = {1,6,3,2,5}; // vector<int> p = {1,3,2,6,5}; vector<int> p = {5, 2, -17, -11, 25, 76, 62, 98, 92, 61}; Solution s; cout << s.verifyPostorder(p) << endl; return 0; }
解题思路:
注意二叉搜索树树的特点: 1. 二叉搜索树BST:对于所有子树满足:左结点小于父节点,父节点小于右结点;(因此,左子树所有结点小于根节点,右子树所有结点大于根节点) 2. 二叉搜索树BST后续遍历数组特点:数组最后一个元素一定是根节点,数组左半部分为根节点的左子树,右半部分为根节点的右子树;且左半部分结点小于根节点,右半部分节点的值大于根节点
参考大佬们的题解:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/solution/mian-shi-ti-33-er-cha-sou-suo-shu-de-hou-xu-bian-6/
34. 剑指 Offer 34. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 target = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
提示:节点总数 <= 10000
c++解法:
#include<iostream> #include<vector> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode *left; TreeNode *right; TreeNode(): val(0), left(NULL), right(NULL){}; TreeNode(int x):val(x), left(NULL),right(NULL){}; TreeNode(int x, TreeNode *left, TreeNode *right):val(x),left(left),right(right){}; }; class Solutuon{ public: vector<vector<int>> ret; vector<int> path; vector<vector<int>> pathSum(TreeNode* root, int target){ recurTree(root, target); return ret; } void recurTree(TreeNode* root, int target){ if(!root) return; path.push_back(root->val); target -= root->val; if(target==0 && !root->left && !root->right){ //题目要求是根节点到叶节点的路径,必须是叶子节点!!!! vector<int> pathCopy(path); // 深度拷贝,防止后续被修改 ret.push_back(pathCopy); } recurTree(root->left, target); recurTree(root->right, target); path.pop_back(); } }; //构建二叉树 template<typename T, int N> TreeNode* initTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); queue<TreeNode*> q; q.push(head); int i=1; while(i<N && !q.empty()){ TreeNode* temp = q.front(); q.pop(); if(nums[i]!='\0'){ temp->left = new TreeNode(nums[i]); q.push(temp->left); } if(i+1<N && nums[i+1]!='\0'){ temp->right = new TreeNode(nums[i+1]); q.push(temp->right); } i = i+2; } return head; } //层次遍历打印二叉树 void levelOrder(TreeNode* head){ queue<TreeNode* > q; q.push(head); int s = 0; cout << "***start of tree***" <<endl; while(!q.empty()){ s = q.size(); for(int i=0; i<s; i++){ TreeNode* temp = q.front(); q.pop(); cout <<temp->val <<","; if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); } cout << endl; } cout << "***end of tree***" <<endl; } int main(int argc, char* argv[]){ int nums[] = {5,4,8,11,'\0',13,4,7,2,'\0','\0',5,1}; // int nums[] = {1,2}; TreeNode* head = initTree(nums); levelOrder(head); Solutuon s; vector<vector<int>> ret = s.pathSum(head, 22); // vector<vector<int>> ret = s.pathSum(head, 1); for(int i=0; i<ret.size(); i++){ for(int j=0; j<ret[i].size(); j++){ cout << ret[i][j] << ","; } cout << endl; } return 0; }
解题思路:标准的二叉树递归和回溯
两个注意点: 1. 注意题目要求是根节点到叶节点的路径,必须是到最后一层的叶子节点!!!! 2. 添加内层vector时,一定要深度拷贝,否则vector后面会被修改
35. 剑指 Offer 35. 复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
提示:
-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。
c++解法
#include<iostream> class Node{ public: int val; Node* next; Node* random; Node(int _val){ val = _val; next = NULL; random = NULL; } }; class Solution{ public: Node* copyRandomList(Node* head){ if(!head) return NULL; Node *tmp, *cur=head; //新链表和原始链表组合成交替连接的链表 while(cur){ tmp = new Node(cur->val); tmp->next = cur->next; cur->next = tmp; cur = tmp->next; } cur = head; //借助组合链表,构建random while(cur){ if(cur->random){ cur->next->random = cur->random->next; } cur = cur->next->next; } cur = head->next; Node* prev = head, *chead = head->next; //拆分组合链表,还原原始链表的结构 while(cur){ prev->next = cur->next; if(cur->next) cur->next = cur->next->next; prev = prev->next; cur = cur->next; } return chead; } };
解题思路:(参考:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/jian-zhi-offer-35-fu-za-lian-biao-de-fu-zhi-ha-xi-/)

36. 剑指 Offer 36. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:

我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。

特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
c++解法:
#include<iostream> class Node{ public: int val; Node* left; Node* right; Node(){} Node(int _val){ val=_val; left = NULL; right=NULL; } Node(int _val, Node* _left, Node* _right){ val=_val; left = _left; right=_right; } }; class Solution{ public: Node *head, *prev; Node* treeToDoublyList(Node* root){ if(!root) return NULL; midOrder(root); prev->right = head; head->left = prev; return head; } void midOrder(Node* cur){ if(!cur) return; midOrder(cur->left); if(prev){ prev->right = cur; cur->left = prev; } else{ head = cur; // 最左端的结点 } prev = cur; midOrder(cur->right); } };
解题思路:
1. 二叉搜索树中序遍历,即按从小到大顺序排列 2.二叉搜索树的最左端是第一个节点,最优端是最后一个结点 3. 中序遍历过程中,建立链表的指针关系
37. 剑指 Offer 37. 序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树。
你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示:输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

示例:
输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
c++解法:
#include<iostream> #include<string> #include<queue> #include<sstream> #include<vector> using namespace std; struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int x): val(x), left(NULL), right(NULL){} }; class Solution{ public: string serialize(TreeNode* root){ if(!root) return ""; string s; queue<TreeNode*> q; q.push(root); while(!q.empty()){ TreeNode* temp = q.front(); q.pop(); if(temp){ s.append(to_string(temp->val)); q.push(temp->left); q.push(temp->right); }else{ s.append("null"); } s.append(","); } //去除末尾多余的null, size_t n = s.size(); while(n>=5 && s.substr(n-5, 5)=="null,"){ n -= 5; } s.erase(n-1); // n-1是因为末尾还有一个逗号 return s; } TreeNode* deserialize(string data){ if(data=="") return NULL; // 对字符串,根据逗号进行split vector<string> vs; size_t index = data.find(","); vs.push_back(data.substr(0, index)); while(index!=string::npos){ size_t nextIndex = data.find(",",index+1); vs.push_back(data.substr(index+1, nextIndex-index-1)); index = nextIndex; } // for(int i=0; i<vs.size(); i++){ // cout << vs[i] << endl; // } int n = vs.size(); int i=1; TreeNode* head = new TreeNode(stoi(vs[0])); queue<TreeNode*> qt; qt.push(head); while(i<n && !qt.empty()){ TreeNode* temp = qt.front(); qt.pop(); if(vs[i]!="null"){ temp->left = new TreeNode(stoi(vs[i])); qt.push(temp->left); } if(i+1<n && vs[i+1]!="null"){ temp->right = new TreeNode(stoi(vs[i+1])); qt.push(temp->right); } i += 2; } return head; } }; void levelOrder(TreeNode* head){ queue<TreeNode*> q; q.push(head); cout << "*********start of tree******" << endl; while(!q.empty()){ int n = q.size(); for(int i=0; i<n; i++){ TreeNode* temp = q.front(); q.pop(); cout << temp->val << ","; if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); } cout << endl; } cout << "*****end of tree******" << endl; } int main(int argc, char* argv[]){ string data = "1,2,3,null,null,4,5"; // string data = "1"; Solution s; TreeNode* head = s.deserialize(data); levelOrder(head); string dataConvert = s.serialize(head); cout << dataConvert<<endl; return 0; }
解题思路:
1.序列化的时候层次遍历二叉树,将val用逗号分隔组成字符串。(注意加入null结点) 2. 反序列化时,对字符串根据逗号split,最后通过队列组建二叉树
38. 剑指 Offer 38. 字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
限制:
1 <= s 的长度 <= 8
c++解法:
#include<iostream> #include<vector> #include<string> #include<algorithm> #include<set> using namespace std; //方法一:递归(深度优先搜索) class Solutuion{ public: vector<string> permutation(string s){ dfs(s, 0); return res; } private: vector<string> res; void dfs(string s , int x){ if(x==s.size()-1){ res.push_back(s); return; } set<int> st; for(int i=x; i<s.size(); i++){ if(st.find(s[i])!=st.end()) continue; // 防止重复 st.insert(s[i]); swap(s[i], s[x]); dfs(s, x+1); swap(s[i], s[x]); } } }; //方法二:非递归 // class Solutuion{ // public: // vector<string> permutation(string s){ // vector<string> ret; // sort(s.begin(), s.end()); // do { // ret.push_back(s); // } while (nextPermutation(s)); // return ret; // } // //插件c++标准库函数next_permutation() // // https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/solution/zi-fu-chuan-de-pai-lie-by-leetcode-solut-hhvs/ // bool nextPermutation(string& s) { // int i = s.size() - 2; // while (i >= 0 && s[i] >= s[i + 1]) { // i--; // } // if (i < 0) { // return false; // } // int j = s.size() - 1; // while (j >= 0 && s[i] >= s[j]) { // j--; // } // swap(s[i], s[j]); // reverse(s.begin() + i + 1, s.end()); // return true; // } // }; int main(int argc, char* argv[]){ string s = "abc"; Solutuion sl; vector<string> res = sl.permutation(s); for(int i=0; i<res.size(); i++){ cout <<res[i] <<endl; } cout <<endl; return 0; }
39. 剑指 Offer 39. 数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
限制:
1 <= 数组长度 <= 50000
c++解法:
#include<iostream> #include<vector> #include<map> using namespace std; //方法一:摩尔投票法(核心理念为 票数正负抵消 , 如果我们把众数记为 +1,把其他数记为 −1,将它们全部加起来,显然和大于 0,从结果本身我们可以看出众数比其他数多。) class Solution{ public: int majorityElement(vector<int>& nums){ int count=0; int maxNum; for(int i=0; i<nums.size(); i++){ if(count==0) maxNum = nums[i]; count += (nums[i]==maxNum) ? 1 : -1; } return maxNum; } }; //方法二:哈希计数法 // class Solution{ // public: // int majorityElement(vector<int>& nums){ // map<int, int> mp; // int n = nums.size(); // int maxNum = nums[0]; // mp[maxNum] = 1; // for(int i=1; i<n; i++){ // if(mp.find(nums[i])!=mp.end()){ // mp[nums[i]] += 1; // if(mp[nums[i]]>mp[maxNum]) maxNum = nums[i]; // if(mp[nums[i]]>n/2) return maxNum; // }else{ // mp[nums[i]] = 1; // } // } // return maxNum; // } // }; int main(int argc, char* argv[]){ // vector<int> nums = {1, 2, 3, 2, 2, 2, 5, 4, 2}; vector<int> nums = {3,2,3}; Solution s; cout << s.majorityElement(nums) << endl; return 0; }
解题思路:
本题常见的三种解法: 哈希表统计法: 遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。此方法时间和空间复杂度均为 O(N)O(N) 。 数组排序法: 将数组 nums 排序,数组中点的元素一定为众数。 摩尔投票法: 核心理念为 票数正负抵消 。此方法时间和空间复杂度分别为 O(N)O(N) 和 O(1)O(1) ,为本题的最佳解法。

40. 剑指 Offer 40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
c++解法:
#include<iostream> #include<vector> #include<queue> // priority_queue在头文件queue中 using namespace std; // 方法一:快速排序。(注意:快排在这里的功能不是排序整个数组,而是搜索并返回最小的 k个数。) // class Solution{ // public: // vector<int> getLeastNumber(vector<int>& arr, int k){ // if(k>=arr.size()) return arr; // return quick_sort(arr, 0, arr.size()-1, k); // } // vector<int> quick_sort(vector<int>& arr, int left, int right, int k){ // int i=left+1, j=right, pivot=left; // while(i<=j){ // while(i<=j && arr[i]<=arr[pivot]) i++; // while(i<=j && arr[j]>=arr[pivot]) j--; // if(i<j) swap(arr[i], arr[j]); // } // swap(arr[j], arr[pivot]); // if(k<j) quick_sort(arr, left, j-1, k); // if(k>j) quick_sort(arr, j+1, right, k); // return vector<int>(arr.begin(), arr.begin()+k); // } // }; // 方法二:大顶堆。(注意思考:为啥建立的是含有K个元素的大顶堆,不是小顶堆?) class Solution{ public: vector<int> getLeastNumber(vector<int>& arr, int k){ if(k>=arr.size()) return arr; // if(k==0) return {}; priority_queue<int> mHeap; // 优先队列,这里默认为大顶堆(堆中所有元素都比堆顶元素小) // 1. 建立含有k个元素的大顶堆 for(int i=0; i<k; i++){ mHeap.push(arr[i]); } //2. 比堆顶元素小的,弹出堆顶元素,加入新的元素,维持堆元素的个数不变 for(int i=k; i<arr.size(); i++){ if(!mHeap.empty() && arr[i]<mHeap.top()){ mHeap.pop(); mHeap.push(arr[i]); } } // 3. 最后堆中k给元素就是最小的k个 vector<int> ret; while(!mHeap.empty()){ ret.push_back(mHeap.top()); mHeap.pop(); } return ret; } }; int main(int argc, char* argv[]){ // vector<int> nums = {3,2,1}; // vector<int> nums = {0,1,2,1}; vector<int> nums = {0,0,0,2,0,5}; Solution s; vector<int> ret = s.getLeastNumber(nums, 0); for(int i:ret){ cout << i << ","; } cout << endl; return 0; }
解题思路:

41. 剑指 Offer 41. 数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
限制:
最多会对 addNum、findMedian 进行 50000 次调用。
c++解法:
#include<iostream> #include<queue> using namespace std; // class MedianFinder{ // public: // priority_queue<int> maxHeap; // 大顶堆 // priority_queue<int> minHeap; // 小顶堆 // MedianFinder(){ // } // void addNum(int num){ // // 1. 判断元素是加入大顶堆,还是小顶堆 // if(!maxHeap.empty() && num<=maxHeap.top()){ // 小于大顶堆堆顶元素,加入大顶堆 // maxHeap.push(num); // }else if (!minHeap.empty() && num>abs(minHeap.top())) // 大于小顶堆堆顶元素,加入小顶堆 // { // minHeap.push(-num); // } // else{ // maxHeap.push(num); // a.小于小顶堆堆顶元素,大于大顶堆堆顶元素,加入大顶堆;b.大顶堆或小顶堆为空时,加入大顶堆 // } // // cout << minHeap.size()-maxHeap.size() << endl; // 注意这里size为无符号数,直接相减,负数时会异常 // // 2. 维持大顶堆和小顶堆元素个数差不超过2 // if(maxHeap.size() >= minHeap.size()+2){ // 将大顶堆堆顶元素弹出,并加入到小顶堆 // minHeap.push(-maxHeap.top()); // maxHeap.pop(); // }else if( minHeap.size()>= maxHeap.size()+2 ){ // 将小顶堆堆顶元素弹出,并加入到大顶堆 // maxHeap.push(-minHeap.top()); // minHeap.pop(); // } // } // double findMedian(){ // if(maxHeap.empty() && minHeap.empty()) return 0; // 都为空时,异常 // if(maxHeap.size()==1+minHeap.size()){ // 大顶堆元素多1个 // return maxHeap.top(); // }else if(minHeap.size()==1+maxHeap.size()){ // 小顶堆元素多1个 // return -minHeap.top(); // }else{ // return (double)(maxHeap.top() + (-minHeap.top())) /2; // 大顶堆和小顶堆元素一样多 // } // } // }; // 优化后方法: class MedianFinder { public: // 最大堆,存储左边一半的数据,堆顶为最大值 priority_queue<int, vector<int>, less<int>> maxHeap; // 最小堆, 存储右边一半的数据,堆顶为最小值 priority_queue<int, vector<int>, greater<int>> minHeap; /** initialize your data structure here. */ MedianFinder() { } // 维持堆数据平衡,并保证左边堆的最大值小于或等于右边堆的最小值 void addNum(int num) { /* * 当两堆的数据个数相等时候,左边堆添加元素。 * 采用的方法不是直接将数据插入左边堆,而是将数据先插入右边堆,算法调整后 * 将堆顶的数据插入到左边堆,这样保证左边堆插入的元素始终是右边堆的最小值。 * 同理左边数据多,往右边堆添加数据的时候,先将数据放入左边堆,选出最大值放到右边堆中。 */ if (maxHeap.size() == minHeap.size()) { minHeap.push(num); int top = minHeap.top(); minHeap.pop(); maxHeap.push(top); } else { maxHeap.push(num); int top = maxHeap.top(); maxHeap.pop(); minHeap.push(top); } } double findMedian() { if (maxHeap.size() == minHeap.size()) { return (maxHeap.top()+minHeap.top())*1.0/2; } else { return maxHeap.top()*1.0; } } }; int main(int argc, char* argv[]){ MedianFinder* obj = new MedianFinder(); obj->addNum(1); obj->addNum(2); cout << obj->findMedian() << endl; obj->addNum(3); cout << obj->findMedian() << endl; // MedianFinder* obj = new MedianFinder(); // obj->addNum(2); // cout << obj->findMedian() << endl; // obj->addNum(3); // cout << obj->findMedian() << endl; return 0; }
解题思路:维持一个大顶堆和小顶堆:大顶堆的堆顶元素小于小顶堆的堆顶元素, 且两个堆的元素个数之差小于等于1。这样中位数就是两个堆顶元素中的某一个,或者两者平均值

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
c++解法
#include<iostream> #include<vector> using namespace std; class Solution{ public: int maxSubArray(vector<int>& nums){ int dp_value = nums[0], max_sum=nums[0]; // 动态规划:dp_value表示排在当前位置前面的所有元素组成的连续子数组的最大和 for(int i=1; i<nums.size(); i++){ dp_value = max(dp_value+nums[i], nums[i]); // 动态转移方程:dp[i] = max(dp[i-1]+nums[i], nums[i]) if(dp_value>max_sum) max_sum = dp_value; } return max_sum; } }; int main(int argc, char* argv[]){ vector<int> nums = {-2,1,-3,4,-1,2,1,-5,4}; Solution s; cout << s.maxSubArray(nums) << endl; return 0; }
解体思路: 动态规划

43. 剑指 Offer 43. 1~n 整数中 1 出现的次数
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
示例 1:
输入:n = 12 输出:5
示例 2:
输入:n = 13 输出:6
限制:
1 <= n < 2^31
c++解法:
class Solution { public: int countDigitOne(int n) { int low=0, count = 0; long digit=1; //用int,会发生溢出 int cur = n%10, high = n/10; while(high!=0 || cur !=0){ if(cur==0){ count += high*digit; }else if (cur==1) { count += high*digit + low + 1; }else{ count += (high+1)*digit; } low = int(cur*digit + low); cur = high%10; high = high/10; digit *= 10; } return count; } };
解题思路:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/mian-shi-ti-43-1n-zheng-shu-zhong-1-chu-xian-de-2/
找规律: 1.将 n 的个位、十位、百位、...的 1 出现次数相加,即为 1出现的总次数。 2. 当某位为0,1,其他时,寻找该位出现1的个数的规律

44. 剑指 Offer 44. 数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字
示例 1:
输入:n = 3 输出:3
示例 2:
输入:n = 11 输出:0
限制:
0 <= n < 2^31
c++解法
#include<iostream> #include<sstream> #include<string> /* 数字范围 数量 位数 占多少位 1-9 9 1 9 10-99 90 2 180 100-999 900 3 2700 1000-9999 9000 4 36000 ... 例如 2901 = 9 + 180 + 2700 + 12 即一定是4位数,第12位 n = 12; 数据为 = 1000 + (12 - 1)/ 4 = 1000 + 2 = 1002 定位1002中的位置 = (n - 1) % 4 = 3 s[3] = 2; */ using namespace std; class Solution{ public: int findNthDigit(int n){ // if(n<=9) return n; int digit = 1, start = 1; unsigned int num = 9; //注意溢出 while(n>num){ n = n - num; digit += 1; start *= 10; num = (unsigned) digit*start*9; } int quotient = (n-1)/digit; int remainder = (n-1)%digit; string s = to_string(start + quotient); return s[remainder]-'0'; } }; int main(int argc, char* argv[]){ Solution s; cout <<s.findNthDigit(3) <<endl; cout <<s.findNthDigit(11) <<endl; cout <<s.findNthDigit(2901) <<endl; cout <<s.findNthDigit(1000000000) <<endl; return 0; }
45. 剑指 Offer 45. 把数组排成最小的数
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: "102"
示例 2:
输入: [3,30,34,5,9]
输出: "3033459"
提示:
0 < nums.length <= 100
说明:
输出结果可能非常大,所以你需要返回一个字符串而不是整数
拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0
c++解法
#include<iostream> #include<string> #include<vector> using namespace std; //采用快排对字符串进行排序,注意这里的排序规则 class Solution{ public: string minNumber(vector<int>& nums){ vector<string> strs; for(int i=0; i<nums.size(); i++){ strs.push_back(to_string(nums[i])); } quickSort(strs, 0, strs.size()-1); string s=""; for(int i=0; i<strs.size(); i++){ s.append(strs[i]); } return s; } void quickSort(vector<string>& strs, int first, int last){ if(first >= last) return; int left=first+1, right=last, pivot=first; while(left<=right){ while(left<=right && (strs[pivot]+strs[left]) >= (strs[left]+strs[pivot]) ){ left++; } while(left<=right && (strs[pivot]+ strs[right]) <= (strs[right] + strs[pivot]) ){ // 说明strs[right]应该排在strs[pivot]的后面,即题目所描述的大于 right--; } if(left<right) swap(strs[left], strs[right]); } swap(strs[pivot], strs[right]); quickSort(strs, first, right-1); quickSort(strs, right+1, last); } }; int main(int argc, char* argv[]){ // vector<int> nums = {10,2}; vector<int> nums = {3,30,34,5,9}; // vector<int> nums = {41,23,87,55,50,53,18,9,39,63,35,33,54,25,26,49,74,61,32,81,97,99,38,96,22,95,35,57,80,80,16,22,17,13,89,11,75,98,57,81,69,8,10,85,13,49,66,94,80,25,13,85,55,12,87,50,28,96,80,43,10,24,88,52,16,92,61,28,26,78,28,28,16,1,56,31,47,85,27,30,85,2,30,51,84,50,3,14,97,9,91,90,63,90,92,89,76,76,67,55}; Solution s; cout << s.minNumber(nums) << endl; return 0; }
解体思路: 这道题最重要的是理解排序规则:x排在y前面,需要满足什么条件?
https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/solution/mian-shi-ti-45-ba-shu-zu-pai-cheng-zui-xiao-de-s-4/

46. 剑指 Offer 46. 把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示:
0 <= num < 231
c++解法:
#include<iostream> #include<string> #include<sstream> using namespace std; // //方法一:递归 // class Solution{ // public: // int count; // int translateNum(int num){ // string s = to_string(num); // recur(s, 0); // return count; // } // void recur(string s, int i){ // if(i>=s.size()-1){ // count += 1; // return; // } // recur(s, i+1); // if(s[i]!='0' && stoi(s.substr(i, 2))<=25) recur(s, i+2); // } // }; //方法二:动态规划 class Solution{ public: int translateNum(int num){ int a=1, b=1, x=0, y=num%10; while(num!=0){ num /= 10; x = num % 10; int temp = x*10+y; int c = (temp>=10 && temp<=25)? a+b : a; b = a; a = c; y = x; } return a; } }; int main(int argc, char* argv[]){ Solution s; // cout <<s.translateNum(12258) << endl; // cout <<s.translateNum(122) << endl; cout <<s.translateNum(506) << endl; return 0; }
解题思路:
1. 递归:
2. 动态规划:

47. 剑指 Offer 47. 礼物的最大价值
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
提示:
0 < grid.length <= 200
0 < grid[0].length <= 200
c++解法:
#include<iostream> #include<vector> #include<algorithm> using namespace std; // class Solution{ // public: // int maxValue(vector<vector<int>>& grid){ // int m=grid.size(), n = grid[0].size(); // vector<vector<int>> dp(m+1, vector<int>(n+1, 0)); // for(int i=1; i<m+1; i++){ // for(int j=1; j<n+1; j++){ // dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i-1][j-1]; // } // } // return dp[m][n]; // } // }; // 直接用grid当做dp数组,节省空间复杂度 class Solution{ public: int maxValue(vector<vector<int>>& grid){ int m=grid.size(), n = grid[0].size(); //初始化第一列 for(int i=1; i<m; i++){ grid[i][0] += grid[i-1][0]; } //初始化第一行 for(int j=1; j<n; j++){ grid[0][j] += grid[0][j-1]; } for(int i=1; i<m; i++){ for(int j=1; j<n; j++){ grid[i][j] += max(grid[i-1][j], grid[i][j-1]); } } return grid[m-1][n-1]; } }; int main(int argc, char* argv[]){ vector<vector<int>> grid = {{1,3,1}, {1,5,1}, {4,2,1}}; Solution s; cout << s.maxValue(grid) << endl; return 0; }
解题思路:动态规划

48. 剑指 Offer 48. 最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
s.length <= 40000
c++解法:
#include<iostream> #include<string> #include<algorithm> #include<map> using namespace std; class Solution{ public: int lengthOfLongestSubstring(string s){ int n = s.size(); if(n==0) return 0; int dp[n]; dp[0] =1; int a=0, b=1; for(int b=1; b<n; b++){ for(int i=a;i<b;i++){ if(s[i]==s[b]) a = i+1; } dp[b] = max(dp[b-1], b-a+1); } return dp[n-1]; } }; // hash+临时数字代替dp数组 // class Solution{ // public: // int lengthOfLongestSubstring(string s){ // int n = s.size(); // if(n==0) return 0; // map<char, int> dict; // int a=-1, res=0; // for(int b=0; b<n; b++){ // if(dict.find(s[b])!=dict.end()) a = max(a, dict[s[b]]); // dict[s[b]] = b; // res= max(res, b-a); // } // return res; // } // }; int main(int argc, char* argv[]){ // string st = "abcabcbb"; // string st = "bbbbb"; string st = "pwwkew"; // string st = "au"; Solution s; cout << s.lengthOfLongestSubstring(st) << endl; return 0; }
解题思路:动态规划

49. 剑指 Offer 49. 丑数
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。\
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
c++解法:
#include<iostream> #include<algorithm> using namespace std; class Solution{ public: int nthUglyNumber(int n){ int a=0, b=0, c=0; // int dp[n] = {1}; //第一项初始化为1,其他补0 int dp[n]; dp[0] = {1}; for(int i=1; i<n; i++){ int na=2*dp[a], nb=3*dp[b], nc=5*dp[c]; dp[i] = min(na, min(nb, nc)); if(dp[i]==na) a+=1; if(dp[i]==nb) b+=1; if(dp[i]==nc) c+=1; } return dp[n-1]; } }; int main(int argc, char* argv[]){ Solution s; cout << s.nthUglyNumber(10) <<endl; cout << s.nthUglyNumber(5) <<endl; return 0; }
解题思路:

50. 剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
限制:
0 <= s 的长度 <= 50000
c++解法:
#include<iostream> #include<string> #include<map> using namespace std; //方法一:采用字典 // class Solution{ // public: // char firstUniqChar(string s){ // int n = s.size(); // map<char, bool> dict; // for(int i=0; i<n; i++){ // if(dict.find(s[i])!=dict.end()){ // dict[s[i]] = false; // }else{ // dict[s[i]] = true; // } // } // for(int i=0; i<n; i++){ // if(dict[s[i]]) return s[i]; // } // return ' '; // } // }; //方法二:采用数组 class Solution{ public: char firstUniqChar(string s){ int n = s.size(); int arr[26] = {0}; for(int i=0; i<n; i++){ arr[s[i]-'a'] += 1; } for(int i=0; i<n; i++){ if(arr[s[i]-'a']==1) return s[i]; } return ' '; } }; int main(int argc, char* argv[]){ Solution s; // string st="abaccdeff"; // string st=""; string st="leetcode"; cout <<s.firstUniqChar(st) << endl; return 0; }
51. 剑指 Offer 51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
c++解法
#include<iostream> #include<vector> using namespace std; // 逆序对总数和归并排序联系在一起 class Solution{ public: int reversePairs(vector<int>& nums){ vector<int> temp(nums.size(),0); return mergeSort(nums, 0, nums.size()-1, temp); } int mergeSort(vector<int>& nums, int left, int right, vector<int>& temp){ if(left>=right) return 0; int mid = (right+left)/2; int res = mergeSort(nums, left, mid, temp) + mergeSort(nums, mid+1, right, temp); int i=left, k=left, j=mid+1; while(i<=mid && j<=right){ if(nums[i]<=nums[j]){ res += (j-mid-1); //比nums[i]小的数字包括:nums[mid+1], nums[mid+2]....nums[j-1] temp[k] = nums[i++]; }else{ temp[k] = nums[j++]; } k++; } while(i<=mid){ res += (j-mid-1); temp[k++] = nums[i++]; } while(j<=right){ temp[k++] = nums[j++]; } copy(temp.begin()+left, temp.begin()+right+1, nums.begin()+left); // cout << res << endl; return res; } }; int main(int argc, char* argv[]){ vector<int> nums = {7,5,6,4}; Solution s; cout << s.reversePairs(nums) << endl; // for(int i=0; i<nums.size(); i++){ // cout << nums[i] << endl; // } return 0; }
解体思路: 逆序对总数和归并排序联系在一起

52. 剑指 Offer 52. 两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点。(公共节点指是结点地址相同,不仅仅是结点的val相同)
如下面的两个链表:

在节点 c1 开始相交。
示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
c++解法:
#include<iostream> using namespace std; struct ListNode{ int val; ListNode* next; ListNode(int x):val(x), next(NULL){}; }; // 双指针法:两链表尾部对齐,长链表先移动 class Solution{ public: ListNode* getIntersectionNode(ListNode* headA, ListNode* headB){ ListNode *pa = headA, *pb=headB; int i=0, j=0; while(pa){ // 链表A的长度 i++; pa = pa->next; } while(pb){ j++; pb = pb->next; // 链表B的长度 } pa=headA; pb=headB; //长链表先移动,直到长短链表长度相等 if(i>j){ while(i>j){ pa = pa->next; i--; } }else{ while(j>i){ pb = pb->next; j--; } } //现在两链表长度相等,同时一起移动 while(pa && pb){ if(pa==pb) break; pa = pa->next; pb = pb->next; } if(pa && pb) return pa; return NULL; } }; //方法二:双指针进阶版 // class Solution{ // public: // ListNode* getIntersectionNode(ListNode* headA, ListNode* headB){ // ListNode *pa = headA, *pb=headB; // while(pa != pb){ // pa = (pa!=NULL)? pa->next: headB; // pb = (pb!=NULL)? pb->next: headA; // } // return pa; // } // }; template<typename T, int N> ListNode* initList(T (&nums)[N]){ ListNode* head = new ListNode(nums[0]); ListNode* temp =head; int i=1; while(i < N){ temp->next = new ListNode(nums[i]); temp = temp->next; i++; } return head; } void printList(ListNode* head){ ListNode* temp = head; while(temp){ cout <<temp->val <<","; temp = temp->next; } cout <<endl; } void makeIntersection(ListNode* headA, ListNode* headB, int i, int j){ ListNode *t1=headA, *t2=headB; while(i>0 && t1){ t1 = t1->next; i--; } while(j>1 && t2){ t2 = t2->next; j--; } if(t1 && t2){ t2->next=t1; } } int main(int argc, char* argv[]){ int listA[] = {4,1,8,4,5}; // int listB[] = {5,0,1,8,4,5}; int listB[] = {5,0,1,2,6,7}; ListNode* headA = initList(listA); printList(headA); ListNode* headB = initList(listB); printList(headB); makeIntersection(headA, headB, 2, 3); // 构造两链表的交点 printList(headB); Solution s; ListNode* inter = s.getIntersectionNode(headA, headB); cout << inter->val << endl; return 0; }
53. 剑指 Offer 53 - I. 在排序数组中查找数字 I
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
c++解法
#include<iostream> #include<vector> using namespace std; //方法三,二次二分法,寻找target和target-1 class Solution{ public: int search(vector<int>& nums, int target){ return helper(nums, target) - helper(nums, target-1); // (target的右边界) - (target-1的右边界) } int helper(vector<int>& nums, int target){ int low=0, high=nums.size()-1; int mid=0; //第一次二分法,寻找target的右边界 while(low<=high){ mid = (int) (low+(high-low)/2); if(nums[mid]>target){ high = mid-1; }else{ low = mid+1; } } return low; // low是第一个大于target的位置 } }; // //方法二,二次二分法 // class Solution{ // public: // int search(vector<int>& nums, int target){ // int low=0, high=nums.size()-1; // int mid=0; // //第一次二分法,寻找target的右边界 // while(low<=high){ // mid = (int) (low+(high-low)/2); // if(nums[mid]>target){ // high = mid-1; // }else{ // low = mid+1; // } // } // int right = low; // 右边界,right是第一个大于target的位置 // if(high>=0 && nums[high]!=target) return 0; //没找到情况下,返回0 // //第一次二分法,寻找target的左边界 // low = 0; // while(low<=high){ // mid = (int) (low+(high-low)/2); // if(nums[mid]<target){ // low = mid+1; // }else{ // high = mid-1; // } // } // int left = high; // 右边界,left是第一个小于target的位置 // return right-left-1; // } // }; //方法一,一次二分法 // class Solution{ // public: // int search(vector<int>& nums, int target){ // if(nums.size()==0) return 0; // int low=0, high=nums.size()-1; // int mid=0, count=0; // //二分法找到target的位置 // while(low<high){ // mid = (int) ((low+high)/2); // if(nums[mid]>target){ // high = mid-1; // }else if (nums[mid]<target) // { // low = mid+1; // }else{ // break; // } // } // //从target位置,向左右两边延伸 // for(int i=mid;i>=0;i--){ // if(nums[i]==target) count++; // } // for(int j=mid+1;j<nums.size();j++){ // if(nums[j]==target) count++; // } // return count; // } // }; int main(int argc, char* argv[]){ vector<int> nums = {5,7,7,8,8,10}; int target = 8; // int target = 6; // vector<int> nums = {}; // int target = 0; Solution s; cout <<s.search(nums, target) <<endl; return 0; }
54. 剑指 Offer 53 - II. 0~n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
限制:
1 <= 数组长度 <= 10000
c++解法:(二分查找法)
#include<iostream> #include<vector> using namespace std; class Solution{ public: int missingNumner(vector<int>& nums){ int left=0, right=nums.size()-1; while(left <= right){ int mid = left + (int)((right-left)/2); if(nums[mid]>mid){ right = mid-1; }else if (nums[mid]==mid) { left = mid+1; } } return left; } }; int main(int argc, char* argv[]){ // vector<int> nums = {0,1,3}; vector<int> nums = {0,1,2,3,4,5,6,7,9}; Solution s; cout << s.missingNumner(nums) << endl; return 0; }
55. 剑指 Offer 54. 二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 4
示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
输出: 4
限制:
1 ≤ k ≤ 二叉搜索树元素个数
c++解法:(按“右子树-根-左子树 ”的顺序遍历)
#include<iostream> #include<vector> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x),left(NULL),right(NULL){} }; class Solution{ public: int kthLargest(TreeNode* root, int k){ m = k; dfs(root); return ret; } // 右,根,左 的遍历顺序 void dfs(TreeNode* root){ if(!root || m<=0) return; // 避免m=0后还继续遍历 dfs(root->right); if(--m ==0){ ret = root->val; return; } dfs(root->left); } private: int ret, m; }; TreeNode* createTree(vector<int> &nums){ TreeNode* head = new TreeNode(nums[0]); queue<TreeNode*> q; q.push(head); int i=1; while(i<nums.size() && (!q.empty())){ TreeNode* temp = q.front(); q.pop(); if(nums[i]!='\0'){ temp->left = new TreeNode(nums[i]); q.push(temp->left); } if(i+1<nums.size() && nums[i+1]!='\0'){ temp->right = new TreeNode(nums[i+1]); q.push(temp->right); } i+=2; } return head; } void printTree(TreeNode* head){ queue<TreeNode*> q; q.push(head); cout << "Start of the tree" <<endl; while(!q.empty()){ int j = q.size(); while(j>0){ TreeNode* temp = q.front(); q.pop(); if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); cout << temp->val <<","; j--; } cout << endl; } cout << "End of the tree"<< endl; } int main(int argc, char* arg[]){ // vector<int> nums = {3,1,4,'\0',2}; vector<int> nums = {5,3,6,2,4,'\0','\0',1}; TreeNode* head = createTree(nums); printTree(head); Solution s; cout <<s.kthLargest(head, 3) <<endl; return 0; }
56. 剑指 Offer 55 - I. 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
提示:
节点总数 <= 10000
c++解法
#include<iostream> #include<cmath> #include<vector> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x), left(NULL), right(NULL){} }; class Solution{ public: int maxDepth(TreeNode* root){ if(!root) return 0; return max(maxDepth(root->left), maxDepth(root->right)) +1; } }; //创建二叉树 TreeNode* createTree(vector<int> &nums){ TreeNode* head = new TreeNode(nums[0]); queue<TreeNode*> q; q.push(head); int i=1; while(i<nums.size() && (!q.empty())){ TreeNode* temp = q.front(); q.pop(); if(nums[i]!='\0'){ temp->left = new TreeNode(nums[i]); q.push(temp->left); } if(i+1<nums.size() && nums[i+1]!='\0'){ temp->right = new TreeNode(nums[i+1]); q.push(temp->right); } i+=2; } return head; } //打印二叉树 void printTree(TreeNode* head){ queue<TreeNode*> q; q.push(head); cout << "Start of the tree" <<endl; while(!q.empty()){ int j = q.size(); while(j>0){ TreeNode* temp = q.front(); q.pop(); if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); cout << temp->val <<","; j--; } cout << endl; } cout << "End of the tree"<< endl; } int main(int argc, char* argv[]){ vector<int> nums = {3,9,20,'\0','\0',15,7}; TreeNode* root = createTree(nums); printTree(root); Solution s; cout <<s.maxDepth(root) << endl; return 0; }
57. 剑指 Offer 55 - II. 平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false 。
限制:
0 <= 树的结点个数 <= 10000
c++解法
#include<iostream> #include<cmath> #include<vector> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x): val(x), left(NULL), right(NULL){} }; //方法三:多次递归 class Solution{ public: bool isBalanced(TreeNode* root){ if(!root) return true; return abs(maxDepth(root->left)-maxDepth(root->right))<=1 && isBalanced(root->left) && isBalanced(root->right); } int maxDepth(TreeNode* root){ if(!root) return 0; return max(maxDepth(root->left), maxDepth(root->right))+1; } }; //方法二:改进方法一,减少遍历 // class Solution{ // public: // bool isBalanced(TreeNode* root){ // return maxDepth(root)!=-1; // } // int maxDepth(TreeNode* root){ // if(!root) return 0; // int l = maxDepth(root->left); // if(l==-1) return -1; //左子树不平衡,直接返回-1 // int r = maxDepth(root->right); // if(r==-1) return -1; //右子树不平衡,直接返回-1 // if(abs(l-r)>1){ // return -1; // 不平衡,返回-1 // }else{ // return max(l, r)+1; //平衡,返回树的深度 // } // } // }; // 方法一:遍历整棵树,记录左右数深度差 // class Solution{ // public: // bool flag=true; // bool isBalanced(TreeNode* root){ // maxDepth(root); // return flag; // } // int maxDepth(TreeNode* root){ // if(!root) return 0; // int l = maxDepth(root->left); // int r = maxDepth(root->right); // flag = flag & (abs(l-r)>1?0:1); // return max(l, r)+1; // } // }; //创建二叉树 TreeNode* createTree(vector<int> &nums){ TreeNode* head = new TreeNode(nums[0]); queue<TreeNode*> q; q.push(head); int i=1; while(i<nums.size() && (!q.empty())){ TreeNode* temp = q.front(); q.pop(); if(nums[i]!='\0'){ temp->left = new TreeNode(nums[i]); q.push(temp->left); } if(i+1<nums.size() && nums[i+1]!='\0'){ temp->right = new TreeNode(nums[i+1]); q.push(temp->right); } i+=2; } return head; } //打印二叉树 void printTree(TreeNode* head){ queue<TreeNode*> q; q.push(head); cout << "Start of the tree" <<endl; while(!q.empty()){ int j = q.size(); while(j>0){ TreeNode* temp = q.front(); q.pop(); if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); cout << temp->val <<","; j--; } cout << endl; } cout << "End of the tree"<< endl; } int main(int argc, char* argv[]){ // vector<int> nums = {3,9,20,'\0','\0',15,7}; // vector<int> nums = {1,2,2,3,3,'\0','\0',4,4}; vector<int> nums = {1,2,2,3,'\0','\0',3,4,'\0','\0',4}; TreeNode* root = createTree(nums); printTree(root); Solution s; cout << s.isBalanced(root) << endl; return 0; }
58. 剑指 Offer 56 - I. 数组中数字出现的次数
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums.length <= 10000
c++解法:
#include<iostream> #include<vector> using namespace std; class Solution{ public: vector<int> singleNumbers(vector<int>& nums){ int ret = 0; // 0与任何数异或为其本身 for(int i=0; i<nums.size(); i++){ ret ^= nums[i]; } // 相同的数异或为0, 最后得到两个不同数a,b的异或结果 int bit = 1; while((ret&bit)==0){ bit <<= 1; //左移1位 } //找到异或值的某一位bit为1,表示a和b在这个位置不同 // 根据a和b的不同,把nums分成两组,相同的数字肯定会分到同一组 int a=0, b=0; for(int i=0; i<nums.size(); i++){ if((nums[i]&bit)==0){ a ^= nums[i]; }else{ b ^= nums[i]; } } return vector<int>{a, b}; } }; int main(int argc, char* argv[]){ // vector<int> nums={4,1,4,6}; vector<int> nums={1,2,10,4,1,4,3,3}; Solution s; vector<int> singles = s.singleNumbers(nums); for(int s:singles){ cout <<s << endl; } cout << endl; return 0; }
解题思路: 分组异或

59. 剑指 Offer 56 - II. 数组中数字出现的次数 II、
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 2^31
c++解法:
#include<iostream> #include<vector> using namespace std; class Solution{ public: int singleNumber(vector<int>& nums){ unsigned int bit=1, res=0; // 无符号数,防止溢出 for(int i=0; i<32; i++){ if(i>0) bit <<= 1; int cnt=0; for(int num:nums){ if((bit & num)!=0) cnt++; //统计该bit位为1的个数,结果为3n,表示单独的那个数字该bit为0;结果为3n+1,表示单独的那个数字该bit为1 } if((cnt%3)!=0) res |= bit; } return res; } }; int main(int argc, char* argv[]){ // vector<int> nums = {3,4,3,3}; vector<int> nums = {9,1,7,9,7,9,7}; Solution s; cout << s.singleNumber(nums) << endl; return 0; }
解题思路:
解题思路: 1. 总体思路:判断该数字在每一个比特位置(0-31比特位)上是0还是1,判断方法如下: 对于每一个比特位,统计数组中该bit位为1的个数,若结果为3n,表示单独的那个数字该bit为0;若结果为3n+1,表示单独的那个数字该bit为1
60. 剑指 Offer 57. 和为s的两个数字
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
限制:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6
c++解法:
#include<iostream> #include<vector> using namespace std; class Solution{ public: vector<int> twoSum(vector<int>& nums, int target){ int i=0, j=nums.size()-1; while(i<j){ int mid = i+(int)((j-i)/2); // 先二分法定位到比target小的区间 if(nums[mid]>target){ j = mid -1; }else{ //在区间内左右指针向中间移动 if(nums[i]+nums[j]>target){ j--; }else if(nums[i]+nums[j]<target) { i++; }else{ return vector<int>{nums[i], nums[j]}; } } } return {}; } }; int main(int argc, char* argv[]){ Solution s; // vector<int> nums = {2,7,11,15}; // vector<int> ret =s.twoSum(nums, 9); // vector<int> nums = {10,26,30,31,47,60}; // vector<int> ret =s.twoSum(nums, 40); vector<int> nums = {10,18,25,33,36,50,50,52,57,74}; vector<int> ret =s.twoSum(nums, 126); for(int i=0; i<ret.size(); i++){ cout << ret[i] << ","; } cout << endl; return 0; }
61. 剑指 Offer 57 - II. 和为s的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
限制:
1 <= target <= 10^5
c+解法:(解题思路:滑动窗口)
#include<iostream> #include<vector> using namespace std; class Solution{ public: vector<vector<int>> findContinuousSequence(int target){ int i=1, j=2, sum=3; //滑动窗口, 题目要求至少包含两个数 vector<vector<int>> ret; vector<int> vi; while(i<j){ if(sum<target){ j++; sum += j; }else if(sum>target){ sum -= i; i++; }else{ vi.clear(); for(int k=i; k<=j; k++){ vi.push_back(k); } ret.push_back(vi); sum -= i; i++; } } return ret; } }; int main(int argc, char* argv[]){ Solution s; // vector<vector<int>> ret = s.findContinuousSequence(9); vector<vector<int>> ret = s.findContinuousSequence(15); for(int i=0; i<ret.size(); i++){ for(int j=0; j<ret[i].size();j++){ cout << ret[i][j] << ","; } cout <<endl; } return 0; }
参考:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/shi-yao-shi-hua-dong-chuang-kou-yi-ji-ru-he-yong-h/
61. 剑指 Offer 58 - I. 翻转单词顺序
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
说明:
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
c++解法
#include<iostream> #include<string> using namespace std; class Solution{ public: string reverseWords(string s){ if(s.size()==0) return s; int j = s.size()-1; string ret = ""; int right=-1; while(j>=0){ if(s[j]!=' ' && right==-1){ right=j; if(ret.size()>0) ret.push_back(' '); // 发现新单词的右边边界,记录位置,并添加一个空格 }else if (s[j]==' ' && right!=-1) { ret.append(s.substr(j+1, right-j)); // 发现新单词的左边边界,添加单词 right = -1; } if(j==0 && right!=-1) ret.append(s.substr(j, right-j+1)); // 防止开头没有空白符号,导致找不到单词左边边界 j--; } return ret; } }; int main(int argc, char* argv[]){ // string words = "the sky is blue"; string words = " hello world! "; // string words = "a good example"; Solution s; cout << s.reverseWords(words) << endl; return 0; }
62. 剑指 Offer 58 - II. 左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"
限制:
1 <= k < s.length <= 10000
c++解法
#include<iostream> #include<string> using namespace std; class Solution{ public: string reverseLeftWords(string s, int n){ string ret=""; for(int i=n; i<(n+s.size()); i++){ ret.push_back(s[i%(s.size())]); } return ret; } }; // 方法二 // class Solution{ // public: // string reverseLeftWords(string s, int n){ // return s.substr(n)+s.substr(0, n); // } // }; int main(int argc, char* argv[]){ Solution s; string words = "abcdefg"; cout << s.reverseLeftWords(words, 2) << endl; // string words = "lrloseumgh"; // cout << s.reverseLeftWords(words, 6) << endl; return 0; }
63. 剑指 Offer 59 - I. 滑动窗口的最大值
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:

提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
c++解法:
#include<iostream> #include<vector> #include<deque> using namespace std; // 方法一:采用单调队列(双端队列实现) class Solution{ public: vector<int> maxSlidingWindow(vector<int>& nums, int k){ if(nums.size()==0) return {}; deque<int> q; for(int i=0; i<k; i++){ while(!q.empty() && nums[q.back()]<nums[i]){ q.pop_back(); } q.push_back(i); } vector<int> ret = {nums[q.front()]}; for(int j=k; j<nums.size(); j++){ while(!q.empty() && q.front()<=j-k){ q.pop_front(); } while(!q.empty() && nums[q.back()]<nums[j]){ q.pop_back(); } q.push_back(j); ret.push_back(nums[q.front()]); } return ret; } }; //方法二:类似动态规划的思想 // class Solution{ // public: // vector<int> maxSlidingWindow(vector<int>& nums, int k){ // if(nums.size()==0) return {}; // vector<int> ret; // int maxValue = 0, j=k, maxIndex=-1; // // 初始化,找到第一次窗口的最大值,已经最大值对应的index // for(int i=0; i<k; i++){ // if(nums[i]>=maxValue){ // maxValue = nums[i]; // maxIndex = i; // } // } // ret.push_back(maxValue); // // 每次向后滑动一次 // while(j<=nums.size()-1){ // if(nums[j]>=maxValue){ //当前窗口末尾值大于前一个窗口的最大值 // maxValue = nums[j]; // maxIndex = j; // }else if(j-maxIndex>=k){ // 当前窗口末尾值小于前一个窗口的最大值,而且前一个窗口的最大值不在当前窗口 // maxValue = nums[j]; // maxIndex = j; // for(int i=j-k+1; i<j; i++){ // if(i<j && nums[i]>maxValue){ // maxValue = nums[i]; // maxIndex = i; // } // } // } // ret.push_back(maxValue); // j++; // } // return ret; // } // }; int main(int argc, char* argv[]){ vector<int> nums={1,3,-1,-3,5,3,6,7}; int k=3; // vector<int> nums={1,-1}; // int k=1; Solution s; vector<int> ret = s.maxSlidingWindow(nums, k); for(int i: ret){ cout << i << ","; } cout <<endl; }
解题思路:
方法一:采用单调队列(双端队列实现)
方法二:采用类似动态规划
64. 剑指 Offer 59 - II. 队列的最大值
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5
c++解法:
#include<iostream> #include<queue> #include<deque> using namespace std; class MaxQueue{ public: queue<int> q; // 保存正常的队列元素 deque<int> dq; //维持一个单调队列(储存最大值) MaxQueue(){ } int max_value(){ if(dq.empty()) return -1; return dq.front(); } void push_back(int value){ while(!dq.empty() && dq.back()<value){ dq.pop_back(); } q.push(value); dq.push_back(value); } int pop_front(){ if(q.empty()) return -1; int val = q.front(); if(val==dq.front()) dq.pop_front(); q.pop(); return val; } }; int main(int argc, char* argv[]){ MaxQueue* obj = new MaxQueue(); obj->push_back(1); obj->push_back(2); cout << obj->max_value() << endl; cout << obj->pop_front() << endl; cout << obj->max_value() <<endl; return 0; }
解体思路: 采用单调队列(双端队列实现)
65. 剑指 Offer 60. n个骰子的点数
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
限制:1 <= n <= 11
c++解法: (动态规划)
#include<iostream> #include<vector> using namespace std; // 动态规划,优化存储空间,采用一维的dp数组 class Solution{ public: vector<double> dicesProbability(int n){ vector<double> dp(6, 1.0/6.0); for(int i=2; i<n+1; i++){ vector<double> tmp(5*i+1, 0); // 共i个骰子时,点数范围为i~6i之间, 则总共有(6i-i+1)个数字 for(int j=0; j<dp.size(); j++){ // tmp表示i个骰子的dp数组,dp表示i-1个骰子的dp数组 for(int k=0; k<6; k++){ tmp[j+k] += dp[j]/6.0; // 状态转移 } } dp = tmp; } return dp; } }; // // 动态规划,二维的dp数组(两个状态变量) // class Solution{ // public: // vector<double> dicesProbability(int n){ // vector<vector<double>> dp(n+1, vector<double>(6*n+1, 0.0)); //dp[i][j]:表示i个骰子,点数之和为j的概率 // for(int j=1; j<=6; j++){ // dp[1][j] = 1.0/6.0; // } // for(int i=2; i<n+1; i++){ // for(int j=i; j<6*i+1; j++){ // 共i个骰子时,点数范围为i~6i之间 // for(int k=1; k<=6; k++){ // 前i-1个骰子的点数确定后,第i个骰子点数可以为1~6(每个概率为1/6) // if(j-k<1) break; // dp[i][j] += dp[i-1][j-k]/6; // 状态转移 // } // } // } // return vector<double>(dp[n].begin()+n, dp[n].end()); // } // }; int main(int argc, char* argv[]){ Solution s; vector<double> ret = s.dicesProbability(1); for(int i=0; i<ret.size(); i++){ cout << ret[i] << ","; } cout << endl; return 0; }
状态转移方程:

66. 剑指 Offer 61. 扑克牌中的顺子
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为0 ,可以看成任意数字。A 不能视为14。
示例 1:
输入: [1,2,3,4,5]
输出: True
示例 2:
输入: [0,0,1,2,5]
输出: True
限制:
数组长度为 5
数组的数取值为 [0, 13] .
c++解法
#include<iostream> #include<vector> #include<algorithm> #include<set> using namespace std; // 方法一:5张牌是顺子的条件是:max-min<5,且没有重复值 class Solution{ public: bool isStraight(vector<int>& nums){ sort(nums.begin(), nums.end()); int joker = 0; for(int i=0; i<4; i++){ if(nums[i]==0){ joker++; }else if(nums[i]==nums[i+1]) { return false; } } return (nums[4] - nums[joker])<5; // joker为第一个非0值的index,则nums[joker]即为最小值 } }; // // 方法一(不排序,集合实现):5张牌是顺子的条件是:max-min<5,且没有重复值 // class Solution{ // public: // bool isStraight(vector<int>& nums){ // set<int> s; // int ma=0, mi=14; // for(int i=0; i<nums.size(); i++){ // if(nums[i]==0) continue; // 大小王跳过 // if(s.find(nums[i])!=s.end()) return false; // 重复返回false // s.insert(nums[i]); // ma = max(ma, nums[i]); // 找最大值 // mi = min(mi, nums[i]); // 找最小值 // } // return ma-mi<5; // } // }; // 方法二: 排序,判断重复值,以及大小王的数量是否够用 // class Solution{ // public: // bool isStraight(vector<int>& nums){ // sort(nums.begin(), nums.end()); // int count = 0; // for(int i=0; i<nums.size(); i++){ // if(nums[i]==0){ // count++; // 统计大小王的数量 // }else if (i>0 && nums[i-1]!=0) // { // if(nums[i]==nums[i-1] || nums[i]-nums[i-1]-1>count){ // 重复,或者大小王的数量不够用 // return false; // }else{ // count -= (nums[i]-nums[i-1]-1); // 用掉(nums[i]-nums[i-1]-1)张大小王 // } // } // } // return true; // } // }; int main(int argc, char* argv[]){ // vector<int> nums = {1,2,3,4,5}; // vector<int> nums = {0,0,1,2,5}; vector<int> nums = {3,4,1,8,5}; Solution s; cout << s.isStraight(nums) << endl; return 0; }
解体思路:5张牌是顺子满足的条件是:max - min<5
67. 剑指 Offer 62. 圆圈中最后剩下的数字
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例 1:
输入: n = 5, m = 3
输出: 3
示例 2:
输入: n = 10, m = 17
输出: 2
限制:
1 <= n <= 10^5
1 <= m <= 10^6
c++解法:
#include<iostream> using namespace std; // // 约瑟夫:递归版动态规划 // class Solution{ // public: // int lastRemaining(int n, int m){ // return helper(n, m); // } // int helper(int n, int m){ // if(n==1) return 0; // int x = helper(n-1, m); // return (x+m)%n; // } // }; // 约瑟夫:迭代版动态规划 class Solution{ public: int lastRemaining(int n, int m){ int x=0; for(int i=2; i<n+1; i++){ x = (x+m)%i; //可以理解为:dp[n] = (dp[n-1]+m)%n } return x; } }; int main(int argc, char* argv[]){ Solution s; cout << s.lastRemaining(5, 3) <<endl; cout << s.lastRemaining(10, 17) <<endl; return 0; }
解题思路: 约瑟夫环,采用动态规划
https://blog.csdn.net/u011500062/article/details/72855826

68. 剑指 Offer 63. 股票的最大利润
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
限制:
0 <= 数组长度 <= 10^5
c++解法:
#include<iostream> #include<vector> using namespace std; // 动态规划 // class Solution{ // public: // int maxProfit(vector<int>& prices){ // int n=prices.size(); // if(n==0) return 0; // int minPrice = prices[0]; // vector<int> dp(n, 0); // dp[i]表示截止到i+1天的最大利润 // for(int i=1; i<n; i++){ // if(prices[i]<minPrice) minPrice = prices[i]; // dp[i] = max(dp[i-1], prices[i]-minPrice); // 动态转移方程:prices[i]-minPrice表示今天卖出股票的利润,dp[i-1]表示截止到前一天的最大利润 // } // return dp[n-1]; // } // }; // 动态规划, 节省内存空间 class Solution{ public: int maxProfit(vector<int>& prices){ int n=prices.size(); if(n==0) return 0; int minPrice = prices[0], mProfit=0; for(int i=1; i<n; i++){ if(prices[i]<minPrice) minPrice = prices[i]; mProfit = mProfit>(prices[i]-minPrice)? mProfit:prices[i]-minPrice; // 动态转移方程:prices[i]-minPrice表示今天卖出股票的利润,mProfit表示截止到前一天的最大利润 } return mProfit; } }; int main(int argc, char* argv[]){ vector<int> prices = {7,1,5,3,6,4}; // vector<int> prices = {7,6,4,3,1}; // vector<int> prices = {7,6,1,3,4, 8}; Solution s; cout << s.maxProfit(prices) << endl; return 0; }
解题思路: 动态规划,转移方程如下:

69. 剑指 Offer 64. 求1+2+…+n
求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
输入: n = 3
输出: 6
示例 2:
输入: n = 9
输出: 45
限制:
1 <= n <= 10000
c++解法:
#include<iostream> using namespace std; // 利用&&运算符的短路效应,代替if判断语句 class Solution{ public: int res=0; int sumNums(int n){ n>1 && sumNums(n-1); // n=1时,会停止递归并返回 res += n; return res; } }; // 调用sizeof // class Solution{ // public: // int sumNums(int n){ // bool s[n][n+1]; // return sizeof(s)>>1; // 右移一位表示除2, n(n+1)/2 // } // }; int main(int argc, char* argv[]){ Solution s; cout << s.sumNums(9) <<endl; // cout << s.sumNums(3) <<endl; return 0; }
解题思路: 递归,并利用&&运算的短路效应作为退出条件
70. 剑指 Offer 65. 不用加减乘除做加法
写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1
输出: 2
提示:
a, b 均可能是负数或 0
结果不会溢出 32 位整数
c++解法:
#include<iostream> using namespace std; // 在计算机系统中,数值一律用补码来表示和存储。补码的优势: 加法、减法可以统一处理(CPU只有加法器) class Solution{ public: int add(int a, int b){ while(b!=0){ // 当进位为 0 时跳出 int c = (unsigned int)(a&b) << 1; // 进位, (得用(unsigned int),否则a=-1, b=2时,会报错误:-2147483648的左移,结果是不定的) a ^= b; // 非进位和 b = c; } return a; } }; // // 只适合正数的加法运算 // class Solution{ // public: // int add(int a, int b){ // int ret=0, bit=1; // while(a || b){ // if((1&a) && (1&b)){ // ret |= (bit<<1); // }else if ((1&a) || (1&b)) // { if(ret&bit){ // ret &= (~bit); // ret |= (bit<<1); // }else{ // ret |= bit; // } // } // bit <<= 1; // a >>= 1; // b >>= 1; // } // return ret; // } // }; int main(int argc, char* argv[]){ Solution s; // cout << s.add(1, 1) << endl; // cout << s.add(12, 145) << endl; // cout << s.add(13, 0) << endl; // cout << s.add(13, -5) << endl; // cout << s.add(-13, -5) << endl; cout << s.add(-1, 2) << endl; return 0; }
解题思路:(注意:在计算机系统中,数值一律用补码来表示和存储。补码的优势: 加法、减法可以统一处理(CPU只有加法器))

71. 剑指 Offer 66. 构建乘积数组
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
示例:
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]
提示:
所有元素乘积之和不会溢出 32 位整数
a.length <= 100000
c++解法:(动态规划)
#include<iostream> #include<vector> using namespace std; //动态规划,维持两个dp数组:left和right,left[i]表示a[0]到a[i-1]的乘积,right[i]表示a[i+1]到a[n-1]的乘积 class Solution{ public: vector<int> constructArr(vector<int>& a){ int n = a.size(); if(n==0) return a; int left[n], right[n]; left[0] =1; right[n-1] = 1; vector<int> b; for(int i=1; i<n; i++){ left[i] = left[i-1]*a[i-1]; } for(int j=n-2; j>=0; j--){ right[j] = right[j+1]*a[j+1]; } for(int i=0; i<n; i++){ b.push_back(left[i]*right[i]); } return b; } }; //动态规划,优化空间复杂度 // class Solution{ // public: // vector<int> constructArr(vector<int>& a){ // int n = a.size(); // if(n==0) return a; // int left=1, right=1; // vector<int> b(n, 1); // for(int i=1; i<n; i++){ // left *= a[i-1]; // b[i] *= left; // right *= a[n-i]; // b[n-i-1] *= right; // } // return b; // } // }; int main(int argc, char* argv[]){ vector<int> a = {1,2,3,4,5}; Solution s; vector<int> b = s.constructArr(a); for(int i=0; i<b.size(); i++){ cout<<b[i] << ","; } cout << endl; return 0; }
72. 剑指 Offer 67. 把字符串转换成整数
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
输入: "42"
输出: 42
示例 2:
输入: " -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 5:
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。
c++解法:(细节太多了,注意越界判断)
#include<iostream> #include<string> #include<vector> using namespace std; class Solution{ public: int strToInt(string str){ if(str.size()==0) return 0; int border = INT32_MAX/10; // INT32_MAX:2147483647, 则border=214748364 int ret=0, i=0, sign=1; // 去除空格 while(i<str.size() && str[i]==' '){ i++; } // 正负数符号判断 if(str[i]=='-') sign = -1; if(str[i]=='-' || str[i]=='+') i++; //遍历累加数字 for(int j=i; j<str.size(); j++){ if(!isdigit(str[j])) break; // 不是数字,则退出; if(j==i){ ret = str[j]-'0'; // 第一个数字 }else{ // 后面连续数字 if(ret > border || (ret==border && str[j]>'7')){ // 214748364*10 + 7 是最小值 return sign==1?INT32_MAX:INT32_MIN; // 范围为[-2147483648, 2147483647], INT32_MIN为-2147483648, } ret = ret*10 + (str[j]-'0'); //!!!这里注意先作str[j]-'0' 不然直接加ASCI码出界报错 } } return sign*ret; } }; int main(int argc, char* argv[]){ Solution su; // string s = "42"; // string s = "-42"; // string s = "4193 with words"; // string s = "words and 987"; string s = "-91283472332"; // string s = "-000000000000000000000000000000000000000000000000001"; // string s = "+-2"; // string s = "0-1"; // string s = " 10522545459"; cout << su.strToInt(s) <<endl; return 0; }
73. 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。
c++解法:
#include<iostream> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x), left(NULL),right(NULL){} }; // 方法一:递归 // class Solution{ // public: // TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){ // if(p->val < root->val && q->val < root->val) return lowestCommonAncestor(root->left, p, q); // if(p->val > root->val && q->val > root->val) return lowestCommonAncestor(root->right, p, q); // return root; // p, q在root两侧,或者等于root // } // }; // 方法二:迭代 class Solution{ public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){ while(root){ if(p->val < root->val && q->val < root->val){ root=root->left; }else if (p->val > root->val && q->val > root->val) { root=root->right; }else{ break; } } return root; // p, q在root两侧,或者等于root } }; template<typename T, int N> TreeNode* createTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); queue<TreeNode*> q; q.push(head); int j=1; while(!q.empty() && j<N){ TreeNode* temp = q.front(); q.pop(); if(nums[j]!='\n'){ temp->left = new TreeNode(nums[j]); q.push(temp->left); } if(j<N-1 && nums[j+1]!='\n'){ temp->right = new TreeNode(nums[j+1]); q.push(temp->right); } j += 2; } return head; } void printTree(TreeNode* head){ if(!head) return; printTree(head->left); cout <<head->val << ","; printTree(head->right); } int main(int argc, char* argv[]){ // cout << (-2*4) <<endl; // if((-2*4) <=0) cout << 12 << endl; int nums[] = {6,2,8,0,4,7,9,'\n','\n',3,5}; TreeNode* root = createTree(nums); printTree(root); cout << endl; cout <<"end of the tree" << endl; Solution s; // TreeNode* p = root->left, *q = root->right; TreeNode* p = root->left, *q = root->left->right; TreeNode* ret = s.lowestCommonAncestor(root, p, q); cout <<ret->val << endl; return 0; }
解题思路:
注意到题目中给出的是一棵「二叉搜索树」,因此我们可以快速地找出树中的某个节点以及从根节点到该节点的路径。 当我们分别得到了从根节点到 p 和 q 的路径之后,我们就可以很方便地找到它们的最近公共祖先了。显然,p 和 q 的最近公共祖先就是从根节点到它们路径上的「分岔点」,也就是最后一个相同的节点。 整体的遍历过程: 1. 我们从根节点开始遍历; 2. 如果当前节点的值大于 p 和 q 的值,说明 p 和 q 应该在当前节点的左子树,因此将当前节点移动到它的左子节点; 3. 如果当前节点的值小于 p 和 q 的值,说明 p 和 q 应该在当前节点的右子树,因此将当前节点移动到它的右子节点; 如果当前节点的值不满足上述两条要求,那么说明当前节点就是「分岔点」。此时,p 和 q 要么在当前节点的不同的子树中,要么其中一个就是当前节点。
74. 剑指 Offer 68 - II. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
c++解法:
#include<iostream> #include<queue> using namespace std; struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int x):val(x), left(NULL),right(NULL){} }; //方法一:递归 class Solution{ public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){ if(root==NULL || root->val==p->val || root->val==q->val) return root; // 退出递归 TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); if(left==NULL && right==NULL) return NULL; // 左右子树中都不包括:p,q,p和q公共节点 if(left==NULL) return right; // left==NULL, right!=NULL, 表明右子树中包括:p,q,p和q公共节点 if(right==NULL) return left; // left!=NULL, right==NULL, 表明左子树中包括:p,q,p和q公共节点 return root; // left!=NULL, right!=NULL, 表明root是p和q公共节点 } }; template<typename T, int N> TreeNode* createTree(T (&nums)[N]){ TreeNode* head = new TreeNode(nums[0]); queue<TreeNode*> q; q.push(head); int j=1; while(!q.empty() && j<N){ TreeNode* temp = q.front(); q.pop(); if(nums[j]!='\n'){ temp->left = new TreeNode(nums[j]); q.push(temp->left); } if(j<N-1 && nums[j+1]!='\n'){ temp->right = new TreeNode(nums[j+1]); q.push(temp->right); } j += 2; } return head; } void printTree(TreeNode* head){ if(!head) return; printTree(head->left); cout <<head->val << ","; printTree(head->right); } int main(int argc, char* argv[]){ int nums[] = {3,5,1,6,2,0,8,'\n','\n',7,4}; TreeNode* root = createTree(nums); printTree(root); cout << endl; cout <<"end of the tree" << endl; Solution s; // TreeNode* p = root->left, *q = root->right; TreeNode* p = root->left, *q = root->left->right->right; TreeNode* ret = s.lowestCommonAncestor(root, p, q); cout <<ret->val << endl; return 0; }
解题思路:
递归返回值不太好理解,对着代码和数据,一行一行的屡一下整个过程,会比较容易明白:


浙公网安备 33010602011771号