class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int n = nums.size();
for (auto x : nums)
//首先遍历数组,如果存在某个数不在0到n-1的范围内,则返回-1,防止下标越界
if (x < 0 || x >= n)
return -1;
//把每个数放到对应的位置上,即让 nums[i] = i
for (int i = 0; i < nums.size(); i ++ ) {
while (nums[i] != i) {
if (nums[i] == nums[nums[i]])
return nums[i];
swap(nums[i], nums[nums[i]]);
//每次交换都会将一个数放在正确的位置上,所以swap操作最多会进行 n 次,不会发生死循环
}
}
return -1;
}
};
AcWing 14. 不修改数组找出重复的数字
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
/*
要求不修改数组
抽屉原理+分治
抽屉原理:n+1 个苹果放在 n 个抽屉里,那么至少有一个抽屉中会放两个苹果。
用在这个题目中就是,一共有 n+1 个数,每个数的取值范围是1到n,所以至少会有一个数出现两次。
注意这里与上一道题不一样,上道题数据范围0-n-1,n个数,就是n个苹果,n个抽屉,不能用抽屉原理
采用分治的思想,将每个数的取值的区间[1, n]划分成[1, n/2]和[n/2+1, n]两个子区间,
然后分别统计两个区间中数的个数。
注意这里的区间是指数的取值范围,而不是数组下标。
每次我们可以把区间长度缩小一半,直到区间长度为1
时间复杂度:每次会将区间长度缩小一半,一共会缩小O(logn) 次。
每次统计两个子区间中的数时需要遍历整个数组,时间复杂度是O(n)。
所以总时间复杂度是O(nlogn)。
空间复杂度:代码中没有用到额外的数组,所以额外的空间复杂度是O(1)。
*/
int l = 1, r = nums.size() - 1;
while (l < r) {
int mid = l + r >> 1; // 划分的区间:[l, mid], [mid + 1, r]
int s = 0;
for (auto x : nums) s += x >= mid + 1 && x <= r;
if (s > r - mid) l = mid + 1;
else r = mid;
}
return r;
}
};
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
/*
要求不修改数组
(模拟链表) O(n)
两点前置知识:
1. 如何判断链表是否存在环?
双指针,一快(每次跑两格)一慢(每次跑一格),从链表首部开始遍历
两个指针最终都会进入环内,由于快指针每次比慢指针多走一格,
因此快指针一定能在环内追上慢指针。而如果链表没环,那么快慢指针不会相遇。
2. 对于有环的链表,如何找到环的起点?
基于第一点,快慢指针相遇时,我们可以证明相遇的点与环起点的距离,
一定和链表首部与环起点的距离相等。
我们可以将数组视为一个(或多个链表),每个元素都是一个节点,元素的下标代表节点地址,
元素的值代表next指针,因此,重复的元素意味着两个节点的next指针一样,
即指向同一个节点,因此存在环,且环的起点即重复的元素。
为了找到任意一个环的起点(重复元素),我们只需要拿到一个链表的首部,
然后利用前置知识即可解决问题。显然,0一定是一个链表的首部,
因为所有元素值的范围在1 - n-1之间,即没有节点指向0节点。
题解流程即为:从0开始,快慢指针分别以2、1的速度向前遍历,当它们相遇时,
将快指针置为0,继续分别以1、1的速度向前遍历,当它们再次相遇时,此时它们的下标就是题解。
时间复杂度分析:慢指针每次走一格,刚好遍历到链表尾部(即环起点)处结束,因此复杂度为O(n)
空间复杂度分析:O(1)
*/
int a = 0, b = 0;//都从0开始走
while (true) {
a = nums[a];//这里不会存在走到空节点的情况,直接走就行
b = nums[nums[b]];
if (a == b) {
a = 0;
while (a != b) {
a = nums[a];
b = nums[b];
}
return a;
}
}
return -1;
}
};
AcWing 15. 二维数组中的查找
class Solution {
public:
bool searchArray(vector<vector<int>> array, int target) {
/*
(单调性扫描) O(n+m)
核心在于发现每个子矩阵右上角的数的性质:
因此我们可以从整个矩阵的右上角开始枚举,假设当前枚举的数是x:
如果 x 等于target,则说明我们找到了目标值,返回true;
如果 x 小于target,则 x 左边的数一定都小于target,我们可以直接排除当前一整行的数;
如果 x 大于target,则 x 下边的数一定都大于target,我们可以直接排序当前一整列的数;
排除一整行就是让枚举的点的横坐标加一,排除一整列就是让纵坐标减一。
当我们排除完整个矩阵后仍没有找到目标值时,就说明目标值不存在,返回false。
时间复杂度分析
每一步会排除一行或者一列,矩阵一共有 n 行,m 列,所以最多会进行 n+m 步。
所以时间复杂度是 O(n+m)。
*/
if (array.empty() || array[0].empty()) return false;
int i = 0, j = array[0].size() - 1;
while (i <= array.size()-1 && j >= 0) {
if (array[i][j] == target) return true;
if (array[i][j] > target) j -- ;
else i ++ ;
}
return false;
}
};
AcWing 16. 替换空格
class Solution {
public:
string replaceSpaces(string &str) {
//线性扫描
string res;
for (auto c : str)
if (c == ' ' ) res += "%20";
else res += c;
return res;
}
};
class Solution {
public:
string replaceSpaces(string &str) {
/*
不能用string,需要自己malloc出char数组来存储答案。
(双指针扫描) O(n)
动态地将原数组长度扩大,此时我们就可以使用双指针算法,来降低空间的使用:
首先遍历一遍原数组,求出最终答案的长度length;
将原数组resize成length大小;
使用两个指针,指针i指向原字符串的末尾,指针j指向length的位置;
两个指针分别从后往前遍历,如果str[i] == ' ',则指针j的位置上依次填充'0', '2', '%',
这样倒着看就是"%20";如果str[i] != ' ',则指针j的位置上填充该字符即可。
由于i之前的字符串,在变换之后,长度一定不小于原字符串,所以遍历过程中一定有i <= j,
这样可以保证str[j]不会覆盖还未遍历过的str[i],从而答案是正确的。
*/
int len = 0;
for (auto c : str)
if (c == ' ')
len += 3;
else
len ++ ;
int i = str.size() - 1, j = len - 1;
str.resize(len);
while (i >= 0)
{
if (str[i] == ' ')
{
str[j -- ] = '0';
str[j -- ] = '2';
str[j -- ] = '%';
}
else str[j -- ] = str[i];
i -- ;
}
return str;
}
};
AcWing 17. 从尾到头打印链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
vector<int> printListReversingly(ListNode* head) {
/*
从前往后遍历一遍输入的链表,将结果记录在答案数组中。
最后再将得到的数组逆序即可。
*/
vector<int> res;
for (auto p = head; p; p = p->next) res.push_back(p->val);
reverse(res.begin(), res.end());
return res;
}
};
AcWing 18. 重建二叉树
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
/*
先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;
在中序遍历中找到根节点的位置 k,则 k 左边是左子树的中序遍历,右边是右子树的中序遍历;
假设左子树的中序遍历的长度是 l,则在前序遍历中,根节点后面的 l 个数,是左子树的前序遍历,
剩下的数是右子树的前序遍历;
有了左右子树的前序遍历和中序遍历,我们可以先递归创建出左右子树,然后再创建根节点;
我们在初始化时,用哈希表(unordered_map<int,int>)记录每个值在中序遍历中的位置,
n个节点,递归n次,在递归到每个节点时,在中序遍历中查找根节点位置的操作需要 O(1) 的时间
此时,创建每个节点需要的时间是 O(1),,
所以总时间复杂度是 O(n)
*/
class Solution {
public:
//哈希表记录每个值在中序遍历中的位置
unordered_map<int,int> pos;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
for (int i = 0; i < n; i ++ )
pos[inorder[i]] = i;
return dfs(preorder, inorder, 0, n - 1, 0, n - 1);
}
TreeNode* dfs(vector<int>&pre, vector<int>&in, int pl, int pr, int il, int ir)
{
if (pl > pr) return NULL;
int k = pos[pre[pl]] - il;//找到左子树的节点个数
auto root = new TreeNode(pre[pl]);
root->left = dfs(pre, in, pl + 1, pl + k, il, il + k - 1);
root->right = dfs(pre, in, pl + k + 1, pr, il + k + 1, ir);
return root;
}
};
AcWing 19. 二叉树的下一个节点
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode *father;
* TreeNode(int x) : val(x), left(NULL), right(NULL), father(NULL) {}
* };
*/
class Solution {
public:
TreeNode* inorderSuccessor(TreeNode* p) {
/*
(模拟) O(h)
这道题目就是让我们求二叉树中给定节点的后继。
分情况讨论即可,如图所示:
如果当前节点有右儿子,则右子树中最左侧的节点就是当前节点的后继。比如F的后继是H;
如果当前节点没有右儿子,则需要沿着father域一直向上找,找到第一个是其father左儿子的节点,
该节点的father就是当前节点的后继。比如当前节点是D,则第一个满足是其father左儿子的节点是F,
则C的father就是D的后继,即F是D的后继。
时间复杂度分析
不论往上找还是往下找,总共遍历的节点数都不大于树的高度。所以时间复杂度是 O(h),
其中 h 是树的高度。
*/
if (!p) return NULL;
if (p->right) {
p = p->right;
while (p->left) p = p->left;
return p;
}
while (p->father && p->father->right == p) {
p = p->father;
}
if (p->father) return p->father;
return NULL;
}
};
AcWing 20. 用两个栈实现队列
class CQueue {
public:
/*
维护两个栈,第一个栈支持插入操作,第二个栈支持删除操作
*/
stack<int> stk1,stk2;
CQueue() {
while(stk1.size()) stk1.pop();
while(stk2.size()) stk2.pop();
}
void appendTail(int value) {
stk1.push(value);
}
int deleteHead() {
if(stk2.empty()){
while(stk1.size()){
stk2.push(stk1.top());
stk1.pop();
}
}
if(stk2.empty()) return -1;
else{
int q=stk2.top();
stk2.pop();
return q;
}
}
};
/**
* Your CQueue object will be instantiated and called as such:
* CQueue* obj = new CQueue();
* obj->appendTail(value);
* int param_2 = obj->deleteHead();
*/
/*
这是一道基础题,只要把功能实现对就可以,不需要考虑运行效率。
我们用两个栈来做,一个主栈,用来存储数据;一个辅助栈,用来当缓存。
push(x),我们直接将x插入主栈中即可。
pop(),此时我们需要弹出最先进入栈的元素,也就是栈底元素。
我们可以先将所有元素从主栈中弹出,压入辅助栈中。则辅助栈的栈顶元素就是我们要弹出的元素,
将其弹出即可。然后再将辅助栈中的元素全部弹出,压入主栈中。
peek(),可以用和pop()操作类似的方式,得到最先压入栈的元素。
empty(),直接判断主栈是否为空即可。
时间复杂度分析
push():O(1);
pop(): 每次需要将主栈元素全部弹出,再压入,所以需要 O(n) 的时间;
peek():类似于pop(),需要 O(n) 的时间;
empty():O(1);
*/
class MyQueue {
public:
/** Initialize your data structure here. */
stack<int> stk, cache;
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
stk.push(x);
}
void copy(stack<int> &a, stack<int> &b) {
while (a.size()) {
b.push(a.top());
a.pop();
}
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
copy(stk, cache);
int res = cache.top();
cache.pop();
copy(cache, stk);
return res;
}
/** Get the front element. */
int peek() {
copy(stk, cache);
int res = cache.top();
copy(cache, stk);
return res;
}
/** Returns whether the queue is empty. */
bool empty() {
return stk.empty();
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* bool param_4 = obj.empty();
*/
AcWing 21. 斐波那契数列
class Solution {
public:
int fib(int n) {
int a = 0, b = 1;
if(n==0) return a;
if(n==1) return b;
for (int i = 2; i <= n; i++){
int c=(a+b)%1000000007;
a = b;
b = c;
}
return b;
// int a=0,b=1;
// while(n--){
// int c=(a+b)%1000000007;
// a=b;
// b=c;
// }
// return a;
}
};
/*
矩阵快速幂,O(log n)
*/
class Solution {
typedef long long ll;
const int MOD=1e9+7;
ll a[2][2]{{1,0},{0,1}}, b[2][2]{{1,1},{1,0}}; // a for answer, b for base
public:
int fib(int n) {
while(n){
if(n&1) ab();
bb();
n>>=1;
}
return a[0][1];
}
void ab(){ // a*b
ll t11=a[0][0], t12=a[0][1], t21=a[1][0], t22=a[1][1];
a[0][0]=(t11*b[0][0] + t12*b[1][0])%MOD;
a[0][1]=(t11*b[1][0] + t12*b[1][1])%MOD;
a[1][0]=a[0][1]; // a、b均是斐波那契矩阵中的某一个,所以是对称矩阵
a[1][1]=(t21*b[1][0] + t22*b[1][1])%MOD;
}
void bb(){ // b*b
ll t11=b[0][0], t12=b[0][1], t21=b[1][0], t22=b[1][1];
b[0][0]=(t11*t11 + t12*t21)%MOD;
b[0][1]=(t11*t12 + t12*t22)%MOD;
b[1][0]=b[0][1];
b[1][1]=(t21*t12 + t22*t22)%MOD;
}
};
剑指 Offer 10- II 青蛙跳台阶问题
/*
青蛙跳台阶问题: f(0)=1 , f(1)=1, f(2)=2;
斐波那契数列问题: f(0)=0, f(1)=1, f(2)=1 。
*/
class Solution {
public:
int numWays(int n) {
int a=1,b=1;
while(n--){
int c=(a+b)%1000000007;
a=b;
b=c;
}
return a;
// int a = 1, b = 1;
// for (int i = 2; i <= n; i++){
// int c=(a+b)%1000000007;
// a = b;
// b = c;
// }
// return b;
}
};
AcWing 22. 旋转数组的最小数字
/*
(二分) O(n)O(n)
为了便于分析,我们先将数组中的数画在二维坐标系中,横坐标表示数组下标,纵坐标表示数值,
图中水平的实线段表示相同元素。
我们发现除了最后水平的一段(黑色水平那段)之外,其余部分满足二分性质:竖直虚线左边的数满足
nums[i]≥nums[0];而竖直虚线右边的数不满足这个条件。
分界点就是整个数组的最小值。
所以我们先将最后水平的一段删除即可。
另外,不要忘记处理数组完全单调的特殊情况:
当我们删除最后水平的一段之后,如果剩下的最后一个数大于等于第一个数,则说明数组完全单调。
*/
class Solution {
public:
int findMin(vector<int>& nums) {
int n=nums.size()-1;
if(n<0) return -1;
while(n>0&&nums[n]==nums[0]) n--;
//跳出循环可能为三种情况,n越来越小,小到等于0,也就是nums[n]=nums[0];
//nums[n]>nums[0];nums[n]<nums[0]。
//其中前两种需要排除,最后一种情况需要继续探索
if(n==0||nums[n]>=nums[0]) return nums[0];
int l=0,r=n;
while(l<r){
int mid=(l+r)>>1;
if(nums[mid]<nums[0]) r=mid;
else l=mid+1;
}
return nums[l];
}
};
AcWing 23. 矩阵中的路径
/*
(DFS) O(n^2*3^k)
在深度优先搜索中,最重要的就是考虑好搜索顺序。
我们先枚举单词的起点,然后依次枚举单词的每个字母。
过程中需要将已经使用过的字母改成一个特殊字母,以避免重复使用字符。
时间复杂度分析:单词起点一共有 n^2 个,单词的每个字母一共有上下左右四个方向可以选择,
但由于不能走回头路,所以除了单词首字母外,仅有三种选择。所以总时间复杂度是 O(n^2*3^k)
*/
class Solution {
public:
bool hasPath(vector<vector<char>>& matrix, string &str) {
for(int i=0;i<matrix.size();i++){
for(int j=0;j<matrix[i].size();j++){
if(dfs(matrix,str,0,i,j)) return true;
}
}
return false;
}
bool dfs(vector<vector<char>>&matrix,string& str, int u, int x, int y){
if(matrix[x][y]!=str[u]) return false;
if(u==str.size()-1) return true;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
char t=matrix[x][y];
matrix[x][y]='*';
for(int i=0;i<4;i++){
int a=x+dx[i],b=y+dy[i];
if(a>=0&&a<matrix.size()&&b>=0&&b<matrix[a].size()){
if(dfs(matrix,str,u+1,a,b)) return true;
}
}
matrix[x][y]=t;
return false;
}
};
AcWing 24. 机器人的运动范围
/*
这是一个典型的宽度优先搜索问题,我们从 (0, 0) 点开始,
每次朝上下左右四个方向扩展新的节点即可。dfs在数据较大时,可能会栈溢出
扩展时需要注意新的节点需要满足如下条件:
之前没有遍历过,这个可以用个bool数组来判断;
没有走出边界;
横纵坐标的各位数字之和小于 k;
最坏情况下会遍历方格中的所有点,所以时间复杂度就是 O(nm)
*/
class Solution {
public:
int get_sum(pair<int, int> p) {//计算每个坐标的数位之和
int s = 0;
while (p.first) {
s += p.first % 10;
p.first /= 10;
}
while (p.second) {
s += p.second % 10;
p.second /= 10;
}
return s;
}
int movingCount(int threshold, int rows, int cols)
{
if (!rows || !cols) return 0;//特判边界
queue<pair<int,int>> q;//定义宽搜队列,存放坐标
vector<vector<bool>> st(rows, vector<bool>(cols, false));
//判重数组,其实vector会默认初始化为false
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int res = 0;
q.push({0, 0});//最开始先把起始点入队
while (q.size()) {
auto t = q.front();
q.pop();
//判断当前点是否合法
if (st[t.first][t.second] || get_sum(t) > threshold) continue;
res ++ ;
st[t.first][t.second] = true;//标记一下
for (int i = 0; i < 4; i ++ ) {
int x = t.first + dx[i], y = t.second + dy[i];
if (x >= 0 && x < rows && y >= 0 && y < cols) q.push({x, y});
}
}
return res;
}
};
/*
设置一个数组dp[n+1],dp[ i ]存储绳子长度为i 时的最大乘积。依题意,绳子至少被剪一次,
绳子长度最小为2。外层for循环从绳长为i=2的情况开始依次计算,直到计算到绳长为n的情况。
内层for循环:当绳长为i时,由于已知至少剪一刀,我们索性假设第一刀剪在长度为j的位置
(即第一段绳子长度为j)。剩下的那段长度为( i - j )的绳子就变成了“可剪可不剪”。
那究竟是“不剪了”得到的乘积大呢,还是“继续剪余下的这段”得到乘积更大?
我们不知道,所以需要两种情况都计算一下进行比较。
其中,“不剪了”得到的乘积是j * ( i - j ),
“继续剪”得到的乘积是j * dp[ i - j ]。
取其中的较大值,就是“第一剪在j位置”能得到的最大乘积。
而第一剪的所有可能位置是1,2,…,i-1。依次计算所有可能情况,取最大值即为dp[ i ]的值。
*/
class Solution {
public:
int maxProductAfterCutting(int n) {
vector<int> f(n+1,0);
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++){
f[i]=max(f[i],max( j*(i-j) , j*f[i-j]) );
}
}
return f[n];
}
};
剑指 Offer 14- II 剪绳子 II
class Solution {
public:
int cuttingRope(int n) {
if(n<=3) return n-1;
long long res=1;
if(n%3==1) n-=4,res*=4;
else if(n%3==2) n-=2,res*=2;
while(n) n-=3,(res*=3)%=1000000007;
return res;
}
};
class Solution {
public:
int cuttingRope(int n) {
if(n<=3) return n-1;
int a = n/3, b = n%3;
int mod = 1e9+7;
if(b==0) return pow1(3,a,mod,1);
if(b==1) return pow1(3,a-1,mod,4);
return pow1(3,a,mod,2);
}
long long pow1(long long a,long long n,long long mod,int mul){
long long res = 1;
while(n){
if(n&1) res = (res * a)%mod;
a = (a * a)%mod;
n >>= 1;
}
res = (res*mul)%mod;
return res;
}
};
AcWing 26. 二进制中1的个数
/*
有个难点是如何处理负数。
在C++中如果我们右移一个负整数,系统会自动在最高位补1,这样会导致 n 永远不为0,
就死循环了。
解决办法是把 n 强制转化成无符号整型,这样 n 的二进制表示不会发生改变,
但在右移时系统会自动在最高位补0。
*/
class Solution {
public:
int NumberOf1(int n) {
int res = 0;
for (int i = 0; i < 32; i ++ )
if (n >> i & 1)
//这里不用强转成unsigned int 是因为我这里只是查看倒数第i位,前面不关心
res ++ ;
return res;
}
};
class Solution {
public:
int NumberOf1(int n) {
int res = 0;
while (n) n -= n & -n, res ++ ;
return res;
}
};
class Solution {
public:
int NumberOf1(int n) {
int res = 0;
unsigned int un = n;
while (un) res += un & 1, un >>= 1;//un&1得到un的最后一位
return res;
}
};
AcWing 27. 数值的整数次方
class Solution {
public:
typedef long long LL;
//由于本题的指数是int范围,可能很大,所以需要用快速幂求解
double Power(double x, int n) {
bool is_minus = n < 0;
double res = 1;
LL k = abs(LL(n));
while(k){
if (k & 1) res *= x;
x *= x;
k>>=1;
}
if (is_minus) res = 1 / res;
return res;
}
};
class Solution {
public:
//不考虑n较大的情况
vector<int> printNumbers(int n) {
vector<int> res;
int m = 1;
while(n--) m *= 10;
for (int i = 1; i < m; i++)
{
res.push_back(i);
}
return res;
}
};
class Solution
{
public:
vector<int> printNumbers(int n)
{
vector<int> nums;
string num(n, '0');
while (!Increase(num)) //直到当前的string数字自增发生越界,才退出循环
{
Save(nums, num);
}
return nums;
}
/*实现string数字自增操作*/
bool Increase(string& num)//注意传引用
{
bool IsOverflow = false; //越界判断标记
int carry = 0; //进位标记
int n = num.size();
for (int i = n - 1; i >= 0; i--)
{
int digit = num[i] - '0' + carry; //存储第i位数的int形式
if (i == n - 1) //如果是个位,则需要自增
{
digit++;
}
if (digit >= 10) //如果当前位数超过10,则需要进位
{
if (i == 0) //如果在最高位进位,则已经越界
{
IsOverflow = true;
}
else //如果在非最高位进位,则设置进位标记并记录当前位数
{
carry = 1;
num[i] = digit - 10 + '0';
}
}
else //如果未发生进位,则记录当前位数后可结束循环
{
num[i] = digit + '0';
break;
}
}
return IsOverflow;
}
/*实现字符串数字去掉高位0并转换为int存入nums向量操作*/
void Save(vector<int>& nums, string num)
{
string temp_s = "";
bool IsBeginZero = true; //高位0标记
for (int i = 0; i < num.size(); i++)
{//去掉前面的一堆字符0
if (IsBeginZero && num[i] != '0')
{
IsBeginZero = false;
}
if (!IsBeginZero)
{
temp_s += num[i];
}
}
int temp_i = stoi(temp_s);
nums.push_back(temp_i);
}
};
class Solution
{
public:
vector<int> nums;
vector<int> printNumbers(int n)
{
string num(n, '0');
dfs(num, 0);
return nums;
}
/*递归实现从最高位到最低位的数字全排列*/
void dfs(string& num, int index)
{
if (index == num.size()) //如果索引index指向最低位的右侧,则到达递归边界,保存当前数字后返回
{
Save(num);
return;
}
else
{
for (int i = 0; i <= 9; i++) //每一位数从0到9排列,记录当前位数的一种情况后递归进行下一位数的排列
{
num[index] = '0' + i;
dfs(num, index + 1);
}
}
}
/*实现字符串数字去掉高位0并转换为int存入nums向量操作*/
void Save(string num)
{
string temp_s = "";
bool IsBeginZero = true; //高位0标记
for (int i = 0; i < num.size(); i++)
{
if (IsBeginZero && num[i] != '0')
{
IsBeginZero = false;
}
if (!IsBeginZero)
{
temp_s += num[i];
}
}
if (temp_s != "") //注意全排列递归解法在排列时会产生全0如"00000",导致temp_s为空,此时不能转换为整数
{
int temp_i = stoi(temp_s);
nums.push_back(temp_i);
}
}
};
AcWing 29. 删除链表中重复的节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
/*
为了方便处理边界情况,我们定义一个虚拟元素 dummy指向链表头节点。
然后从前往后扫描整个链表,每次扫描元素相同的一段,如果这段中的元素个数多于1个,
则将整段元素直接删除。
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* head) {
auto dummy = new ListNode(-1);
dummy->next = head;
auto p = dummy;
while (p->next) {//判断p->next这个确认存在的节点是否重复,重复则全删
auto q = p->next;
//q从p->next这个节点一直往后,直到q->val与待判断的节点不相同为止
while (q && p->next->val == q->val) q = q->next;
//如果跳出循环时,q存在,则q是待判断的节点p->next后面第一个不同的节点
if (p->next->next == q) p = p->next;
else p->next = q;//继续判断
}
return dummy->next;
}
};
AcWing 30. 正则表达式匹配
class Solution {
public:
/*
类似dp问题很多,比如给两个字符串,求最长公共子序列,能否匹配
正则表达式匹配,'.' 匹配任意单个字符,'*' 表示它前面的那一个字符可以出现任意多次(包括0次)
动态规划(序列模型):
状态表示 f[i,j]
集合:所有s[1-i]和p[1-j]的匹配方案
属性:bool 是否存在合法方案
状态计算
如果p[j]不是'*',先看s[i]和p[j]是否匹配,两种情况:s[i]==p[j]或者p[j]=='.',并且f[i-1,j-1]也匹配
如果p[j]是'*',枚举一下,这个'*'表示多少个字符,如果是0个字符f[i,j-2],1个字符f[i-1,j-2]&&s[i]匹配
2个字符f[i-2,j-2]&&s[i]匹配&&s[i-1]匹配...
f[i,j] =f[i,j-2] | f[i-1,j-2]&s[i] | f[i-2,j-2]&s[i]&s[i-1]...
f[i-1,j]=f[i-1,j-2] | f[i-2,j-2]&s[i-1] | f[i-3,j-2]s[i-1]&s[i-2]...
上面一个式子: f[i,j] =f[i,j-2] | f[i-1,j] & s[i]匹配 优化和完全背包很像
*/
bool isMatch(string s, string p) {
int n = s.size(), m = p.size();
s = ' ' + s, p = ' ' + p;//下标都从1开始,所以前面补上一个空格
vector<vector<bool>> f(n + 1, vector<bool>(m + 1));//布尔数组
f[0][0] = true;//初始化
for (int i = 0; i <= n; i ++ )//它可以从0开始,因为没有字符的时候也可能匹配
for (int j = 1; j <= m; j ++ ) {
//如果j==0的话,因为f[0][0]已经初始化过了,其他i不为0的情况一定不匹配,所以j从0开始没有意义
//*和前面一个字符看作一个整体,如果遇到类似a*的a的话要跳过a
if (j + 1 <= m && p[j + 1] == '*') continue;
if ( p[j] != '*') {//如果i指向某个非空字符,并且p[j]!='*',i从1开始,否则i-1没有意义
f[i][j] = i &&f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '.');
} else if (p[j] == '*') {
f[i][j] = f[i][j - 2] || i && f[i - 1][j] && (s[i] == p[j - 1] || p[j - 1] == '.');
}
}
return f[n][m];
}
};
class Solution {
public:
//(迭代) O(n) 用栈模拟递归
bool isSymmetric(TreeNode* root) {
if (!root) return true;
stack<TreeNode*> stk;
stk.push(root->left);
stk.push(root->right);
while(stk.size())
{
auto left = stk.top();//成对取出
stk.pop();
auto right = stk.top();
stk.pop();
if(left==NULL&&right==NULL) continue;
if(!left||!right) return false;
if(left->val!=right->val) return false;
stk.push(left->left);//对应插入,注意顺序
stk.push(right->right);
stk.push(left->right);
stk.push(right->left);
}
return true;
}
};
AcWing 40. 顺时针打印矩阵
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
vector<int> res;
if (matrix.empty()) return res;
int n = matrix.size(), m = matrix[0].size();
vector<vector<bool>> st(n, vector<bool>(m, false));
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int x = 0, y = 0, d = 1;
for (int k = 0; k < n * m; k ++ )
{
res.push_back(matrix[x][y]);
st[x][y] = true;
int a = x + dx[d], b = y + dy[d];
if (a < 0 || a >= n || b < 0 || b >= m || st[a][b])
{
d = (d + 1) % 4;
a = x + dx[d], b = y + dy[d];
}
x = a, y = b;
}
return res;
}
};
目录AcWing 13. 找出数组中重复的数字AcWing 14. 不修改数组找出重复的数字AcWing 15. 二维数组中的查找AcWing 13. 找出数组中重复的数字class Solution {public: int duplicateInArray(vector<int>& nums) { int n = nums.size(); for (auto x : nums) //首先遍历数组,如果存在某个数不在0到n-