一、数组
- 二分查找
1、《剑指offer——旋转数组的最小数字》
2、《leetcode35——搜索插入位置》
3、《leetcode33——搜索旋转排序数组》
4、《leetcode81——搜索旋转排序数组Ⅱ》
5、《leetcode74——搜索二维矩阵》
- 排序
1、《P1059——明明的随机数》
2、《leetcode75——颜色分类》
3、《leetcode324——摆动序列Ⅱ》
- 排列
1、《leetcode31——下一个排列》
2、《leetcode46——全排列》
3、《leetcode47——全排列Ⅱ》
- 回溯
1、《leetcode39——组合总和》
2、《leetcode40——组合总和Ⅱ》
3、《leetcode216——组合总和Ⅲ》
4、《leetcode79——单词搜索》
- 动态规划
1、《leetcode64——最小路径和》
2、《leetcode62——不同路径》
3、《leetcode63——不同路径Ⅱ》
4、《leetcode377——组合总和Ⅳ》
5、《leetcode152——乘积最大子序列》
6、《leetcode746——使用最小花费爬楼梯》
7、《leetcode309——最佳买卖股票时机含冷冻期》
- 其它
1、《leetcode41——缺失的第一个正数》
2、《leetcode78——子集》
3、《leetcode90——子集Ⅱ》
4、《leetcode238——除自身以外数组的乘积》
5、《leetcode289——生命游戏》
6、《leetcode11——盛最多水的容器》
7、《leetcode16——最接近的三数之和》
8、《leetcode209——长度最小的子数组》
9、《leetcode611——有效的三角形个数》
10、《leetcode769——最多能完成排序的块》
二、字符串
三、贪心算法
四、深度优先搜索
1、《P1101——单词方阵》
2、《P1141——01迷宫》
3、《P1434——滑雪》
4、《P1433——吃奶酪》
五、广度优先搜索
六、数学
七、链表
八、树
九、其它数据结构
十、模版题
1、《hihocoder1081——最短路一(Dijkstra算法)》
2、《hihocoder1089——最短路二(Floyd算法)》
3、《hihocoder1093——最短路三(SPFA算法)》
4、《hihocoder1097——最小生成树(Prim算法)》
5、《hihocoder1174——拓扑排序一》
1、《剑指offer——旋转数组的最小数字》
题解:根据题意,如果数组旋转的话那么整个数组可以分为两个有序数组,并且最左边的元素一定大于等于最右边的元素。
二分查找,如果中间数比最左边的数字还要大则证明中间数位于左边的有序数组中,那么需要把左区间移动到中间位置。如果中间数比最右边的数还要小,证明中间数位于右边的有序数组中,那么需要把有区间移动到中间位置。最后如果在二分的过程中把左右区间的范围缩小到只包含两个元素的时候,那么右边的肯定是答案。例如(2,1),(3,2)。。。
特殊情况:(1,0,1,1,1),最左边元素中间元素最右边元素。这种情况也符合题目要求,但是不能用上面的二分法进行查找,所以对于这种特殊情况我们只能从头到尾遍历找到最小值。
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
if(rotateArray.empty()) return 0;
if(rotateArray.size() == 1) return rotateArray[0];
int left = 0,right = rotateArray.size()-1;
int mid = left;
//如果数组旋转了的话,最左边一定大于等于最右边,这样才能进入循环里
while(rotateArray[left]>=rotateArray[right])
{
//左右边界是相邻的情况则右边一定是最小的
if(right-left == 1)
return rotateArray[right];
mid = (left+right)/2;
//如果最左边==最右边==中间,则只能顺序查找。(1,0,1,1,1)
if(rotateArray[left] == rotateArray[right]&&rotateArray[left] == rotateArray[mid])
{
int ans = rotateArray[0];
for(int i = 1;i<rotateArray.size();i++)
{
if(rotateArray[i]<ans)
ans = rotateArray[i];
}
return ans;
}
if(rotateArray[mid]>=rotateArray[left])
left = mid;
else if(rotateArray[mid]<=rotateArray[right])
right = mid;
}
return rotateArray[mid];
}
};
2、《leetcode35——搜索插入位置》
题解:常规的一道二分查找题,难度为easy。需要注意的是二分查找结束后都找不到目标值而跳出循环后要返回left,例如(1,3,5,6) target = 2
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0,right = nums.size()-1;
while(left<=right)
{
int mid = (left+right)/2;
if(nums[mid]>target)
right = mid-1;
else if(nums[mid]<target)
left = mid+1;
else return mid;
}
return left;
}
};
3、《leetcode33——搜索旋转排序数组》
题解:与剑指offer那道旋转数组的题目类似,并且这道题说明了没有重复的元素,变得更加容易做。
得到中间数后,如果就是目标值则直接返回下标。如果不是则首先判断中间数是在左边的有序数组里还是在右边的有序数组里,再进一步判断目标值是在中间值的左边还是右边,进而缩小左右区间。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0,right = nums.size()-1;
while(left<=right)
{
int mid = left+(right-left)/2;
if(nums[mid] == target) return mid;
else if(nums[mid]<nums[right])
{
if(nums[mid]<target&&target<=nums[right])
left = mid+1;
else right = mid-1;
}
else
{
if(nums[mid]>target&&target>=nums[left])
right = mid-1;
else left = mid+1;
}
}
return -1;
}
};
4、《leetcode81——搜索旋转排序数组Ⅱ》
题解:跟上一道的唯一区别就是数组允许有重复的数字,那么就会导致这样的情况:(3,1,1)、(1,1,3,1),中间值等于最右值时,目标3既可以在左边又可以在右边,此时我们只需把右区间向左移动一位即可。
class Solution {
public:
bool search(vector<int>& nums, int target) {
if(nums.empty()) return false;
int left = 0,right = nums.size()-1;
while(left<=right)
{
int mid = (left+right)/2;
if(nums[mid] == target) return true;
if(nums[mid]<nums[right])
{
if(nums[mid]<target&&target<=nums[right])
left = mid+1;
else right = mid-1;
}
else if(nums[mid]>nums[right])
{
if(nums[mid]>target&&target>=nums[left])
right = mid-1;
else left = mid+1;
}
else right--;
}
return false;
}
};
5、《leetcode74——搜索二维矩阵》
题解:根据题意,是一个排好序的矩阵,很自然就想到二分法。具体做法是先对矩阵的第一列做二分查找,若能在第一列就找到目标数则直接返回结果,如果对第一列进行二分查找完成后还没找到目标值则对当前行做二分查找。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if(matrix.empty()||matrix[0].empty()) return false;
if(target<matrix[0][0]||target>matrix.back().back()) return false;
int left = 0,right = matrix.size()-1;
while(left<=right)
{
int mid = (left+right)/2;
if(matrix[mid][0] == target) return true;
else if(matrix[mid][0]<target) left = mid+1;
else right = mid-1;
}
int tmp = right;
left = 0;
right = matrix[tmp].size() - 1;
while(left<=right)
{
int mid = (left+right)/2;
if(matrix[tmp][mid] == target) return true;
else if(matrix[tmp][mid]<target) left = mid+1;
else right = mid-1;
}
return false;
}
};
1、《P1059——明明的随机数》
题解:或许这道题很多人第一反应就是想到使用STL里面的set来做,简单又简洁。但是这道题我不打算使用现成的容器,而是使用bitmap来做,当是练习。
一个int类型4个字节,32bit。假如我们让32bit的每一bit都代表一个数字。那么一个int类型变量就可储存32个数字,并且储存进去后已经自动按照顺序排好。题目的数字范围是1~1000.那么我们只需1000/32个大小的数组即可完成工作。 具体怎么实现呢? 例如数字20,则20应该存在数组下标为[20/32],即下标为[0]的位置。知道了在数组的哪个下标位置后,还需要知道它在这个位置的具体哪个bit,其实就是在(1<<20%32)这一个bit上。其它数字同理,具体看代码实现
#include <iostream>
#include <queue>
#include <functional>
#include <algorithm>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <iomanip>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
ll a[40];
int main()
{
int n, t;
while (cin >> n)
{
memset(a, 0, sizeof(a));
int sum = 0;
for (int i = 0; i < n; i++)
{
cin >> t;
//按位与检查该数字已经存不存在,不存在则执行添加操作
if (((a[t / 32]) & (1 << t % 32)) == 0)
{
sum++;
//按位或进行添加
a[t / 32] |= 1 << (t % 32);
}
}
cout << sum << endl;
for (int i = 0; i < 1001; i++)
{
if (a[i / 32] & (1 << i % 32))
{
sum--;
if (sum == 0)
{
cout << i << endl;
break;
}
else cout << i << " ";
}
}
}
system("pause");
return 0;
}
2、《leetcode75——颜色分类》
题解:三种颜色,当完成排列后可看作把整个数组分成三个区间。设计两个个指针,一个white永远指向白色的左边界,一个blue永远指向蓝色的左边界,最开始的时候white先指向第一个位置,假装第一个位置就是白色的右边界,blue指向末尾位置道理相同。
完成前面工作后开始从头到尾遍历数组,如果遇到0(即红色)就把0交换到白色的左边界的位置,同时白色的左边界要向右移动一位;如果遇到2(即蓝色)就把2交换到蓝色的左边界的位置,同时蓝色的左边界要向右移动一位,移动完成后需要i–,因为蓝色左边界的元素我们之前没见过,要进入下一轮的判断。
class Solution {
public:
void sortColors(vector<int>& nums) {
int len = nums.size();
int white = 0,blue = len-1;
for(int i = 0;i<=blue;i++)
{
if(nums[i] == 0)
swap(nums[white++],nums[i]);
else if(nums[i] == 2)
swap(nums[blue--],nums[i--]);
}
}
};
3、《leetcode324——摆动序列Ⅱ》
题解:排好序(升序)然后把排好序的数组从中间分开两部分,先取小的部分(即左边)的最大值再取大的部分(即右边)的最大值,反复这样持续下去
class Solution {
public:
void wiggleSort(vector<int>& nums) {
vector<int> tmp = nums;
int n = nums.size(), k = (n + 1) / 2, j = n;
sort(tmp.begin(), tmp.end());
for (int i = 0; i < n; ++i)
nums[i] = i & 1 ? tmp[--j] : tmp[--k];
}
};
1、《leetcode31——下一个排列》
题解:找规律吧
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
规律:从后往前看,找到第一对后面比前面大的数字,交换这两个数字,然后对前一个数字后面的所有数字进行升序排列。如果后面的数全都是比前面的小,则对整个排列进行升序排序。
例如:1,2,3 。第一对后面比前面大的数字是(2,3),交换它们的位置,变成1,3,2。
接下来对前一个数字后面的所有数字进行升序排列,即对3后面的所有数字进行升序排列,3后面的数字是2,排序完还是2。结果就是1,3,2。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int len = nums.size()-1;
for(int i = len;i>=0;i--)
{
for(int j = len;j>i;j--)
{
if(nums[j]>nums[i])
{
swap(nums[j],nums[i]);
sort(nums.begin()+i+1,nums.end());
return;
}
}
}
sort(nums.begin(),nums.end());
}
};
2、《leetcode46——全排列》
题解:递归写法,每次交换nums里面的两个数字,经过递归可以生成所有的排列情况。暂时组织不出语言去描述解法hhh。其实题目不难,如果用笔在纸上模拟这个过程会更容易理解,笔者第一次做也是用在纸上一步一步模拟了整个过程。
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;
dfs(ans,nums,0);
return ans;
}
void dfs(vector<vector<int>>& ans,vector<int>& nums,int pos)
{
if(pos == nums.size())
{
ans.push_back(nums);
return;
}
for(int i = pos;i<nums.size();i++)
{
swap(nums[i],nums[pos]);
dfs(ans,nums,pos+1);
swap(nums[i],nums[pos]);
}
}
};
3、《leetcode47——全排列Ⅱ》
题解:这道题笔者直接套用46题的做法,用多一个set进行去重。
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
set<vector<int>> st;
dfs(nums,st,0);
return vector<vector<int>>(st.begin(),st.end());
}
void dfs(vector<int>& nums,set<vector<int>>& st,int pos)
{
if(pos == nums.size())
{
st.insert(nums);
return;
}
for(int i = pos;i<nums.size();i++)
{
swap(nums[i],nums[pos]);
dfs(nums,st,pos+1);
swap(nums[i],nums[pos]);
}
}
};
1、《leetcode64——最小路径和》
题解:根据题意可知,除了第一行和第一列,每次从前一个位置到达后一个位置都有两种走法,一种是向右走,一种是向下走。问最小路径和,那么只需选择这两种走法中路径值最小的即可。本题主要注意处理第一行和第一列的情况就可以了,第一行和第一列的走法其实是固定的。例如第一行的走法只能从前一位走过来,没有第二种走法;第一列也同理。具体实现看代码。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int n = grid.size(),m = n?grid[0].size():0;
for(int i = 0;i<n;i++)
{
for(int j = 0;j<m;j++)
{
if(i == 0&&j == 0) continue;
if(i == 0) grid[i][j] += grid[i][j-1];
else if(j == 0) grid[i][j] += grid[i-1][j];
else grid[i][j] += min(grid[i-1][j],grid[i][j-1]);
}
}
return grid[n-1][m-1];
}
};
2、《leetcode62——不同路径》
题解:本题给出地图的大小要我们求到达终点的总共走法,切入点其实就是第一行和第一列的走法情况,仔细想想不难得出第一列和第一行都只有一种走法,因为无论是横着走还是竖着走都只能是一个方向过来的。其它地方的处理和64题的差不多,只是64题要选出路径值最小的加上去,而这道题是啥都不选,两个选择都加上。
class Solution {
public:
int uniquePaths(int m, int n) {
if(m==0||n==0)
return 0;
vector<vector<int>> path(m,vector<int>(n,0));
for(int i = 0;i<m;i++)
path[i][0] = 1;
for(int i = 0;i<n;i++)
path[0][i] = 1;
for(int i=1;i<m;i++)
{
for(int j = 1;j<n;j++)
{
path[i][j]=path[i-1][j]+path[i][j-1];
}
}
return path[m-1][n-1];
}
};
3、《leetcode63——不同路径Ⅱ》
题解:62题的升级版,多了些障碍物,所以这里只要解决障碍物的问题就跟62题差不多了。解法是先创建一个与原地图大小一样的二维矩阵path,每个位置都初始化为0,0代表到达不了。接下来先遍历地图的第一行,只要没碰到障碍物就给path矩阵相应位置赋值为1;结束后再遍历地图的第一列,也是一样只要没碰到障碍物就给path矩阵相应位置赋值为1。最后从[1][1]这个位置开始从头到尾遍历地图,如果碰到障碍物依然给path矩阵相应位置赋值为1,但是只要没有障碍物就跟62题一样,加上前两个方向的路径值。最后path的末尾就是最终解。
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
if(obstacleGrid.empty()) return 0;
int n = obstacleGrid.size(),m = obstacleGrid[0].size();
vector<vector<int>> path(n,vector<int>(m,0));
for(int i = 0;i<m;i++)
{
if(obstacleGrid[0][i] != 1)
path[0][i] = 1;
else break;
}
for(int i = 0;i<n;i++)
{
if(obstacleGrid[i][0] != 1)
path[i][0] = 1;
else break;
}
for(int i = 1;i<n;i++)
{
for(int j = 1;j<m;j++)
{
if(obstacleGrid[i][j] == 1)
path[i][j] = 0;
else path[i][j]+=path[i-1][j]+path[i][j-1];
}
}
return path[n-1][m-1];
}
};
4、《leetcode377——组合总和Ⅳ》
题解:这道题理论上可以用回溯来做,但是会超时,求组合的个数,可以考虑dp。dp的目的是找到等于目标值的序列和。dp[i]代表的意思是序列和等于i的有几种排列可能,如果i>nums[j],可以知道dp[i]有一部分的组成可以是dp[i-nums[j]]。于是推导公式为dp[i] = dp[i]+dp[i-nums[j]],其中i>=nums[j]。
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1,0);
dp[0] = 1;
for(int i = 1;i<=target;i++)
{
for(int j = 0;j<nums.size();j++)
{
if(i>=nums[j])
dp[i] += dp[i-nums[j]];
}
}
return dp.back();
}
};
5、《leetcode152——乘积最大子序列》
题解:该题是在最大子数组之和演变而来,变得更加复杂。因为在求和的时候,遇到0,不会改变最大值,遇到负数,也只是会减小最大值而已。而在求最大子数组乘积的问题中,遇到0会使整个乘积为0,而遇到负数,则会使最大乘积变成最小乘积,正因为有负数和0的存在,使问题变得复杂了不少。
解决的思路类似暴力,每和数组当前位置的数字相乘,最大的值有可能会变成最小值,而最小值有可能会变成最大值,又可能这个位置的数字本身就是最大值。所以我们要比较这三种情况取最大的。单纯用暴力肯定会超时,只能使用dp解决。具体实现看代码。
class Solution {
public:
int maxProduct(vector<int>& nums) {
if(nums.empty()) return 0;
int Max = nums[0];
int Min = nums[0];
int res = nums[0];
for(int i = 1;i<nums.size();i++)
{
int a = Max*nums[i];
int b = Min*nums[i];
Max = max(max(a,b),nums[i]);
Min = min(min(a,b),nums[i]);
res = max(Max,res);
}
return res;
}
};
6、《leetcode746——使用最小花费爬楼梯》
题解:只需用两个变量a和b来记录前两个值,然后不停的用新得到的值来覆盖它们就好了。我们初始化a和b均为0,然后遍历cost数组,首先将a和b中较小值加上num放入临时变量t中,然后把b赋给a,把t赋给b即可。
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int a = 0,b = 0;
for(int i = 0;i<cost.size();i++)
{
int t = min(a,b)+cost[i];
a = b;
b = t;
}
return min(a,b);
}
};
7、《leetcode309——最佳买卖股票时机含冷冻期》
题解:根据题意,我们会有三种状态
一:某天还没开始买手上持有的资金(1:可能是保持昨天还没买的状态 2:可能昨天是c的状态)。
二:某天买进来等待卖出去手上持有的资金(1:可能是今天买的。2:可能是保持昨天就买的状态)。
三:卖出去的第二天的状态,也就是下一天会进入a的状态
分别对应三个数组 a,b,c,对这三个数组分情况讨论即可。具体看代码注释
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len<=1) return 0;
vector<int> a(len,0); //代表某天还没开始买手上持有的资金(1:可能是保持昨天还没买的状态 2:可能昨天是c的状态)
vector<int> b(len,0); //代表某天买进来等待卖出去手上持有的资金(1:可能是今天买的。2:可能是保持昨天就买的状态)
vector<int> c(len,0); //代表卖出去的第二天的状态,也就是下一天会进入a的状态
b[0] = -prices[0]; //第一天就买,那今天的钱只能是0-prices[0];
c[0] = 0xffffffff;
int res = 0;
//a和c都是处于没有股票的状态,都有可能金钱最大
for(int i = 1;i<len;i++)
{
a[i] = max(a[i-1],c[i-1]);
b[i] = max(b[i-1],a[i-1]-prices[i]);
c[i] = b[i-1]+prices[i];
res = max(res,max(a[i],c[i]));
}
return res;
}
};
1、《leetcode39——组合总和》
题解:典型的回溯做法,声明一个变量cur初始化为0,每次递归都遍历整个candidates数组,加上当前位置的数字进入下一次递归。当cur等于target的时候则满足条件,当cur比target还大的时候直接回溯到上一层递归。需要留意的只有题目允许重复使用相同的元素。具体实现看代码。
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> t;
set<vector<int>> st;
sort(candidates.begin(),candidates.end());
helper(candidates,target,st,t,0);
return vector<vector<int>>(st.begin(),st.end());
}
void helper(vector<int>& candidates,int target,set<vector<int>>& st,vector<int>& t,int cur)
{
if(cur == target)
{
vector<int> temp(t);
sort(temp.begin(),temp.end());
st.insert(temp);
return;
}
if(cur>target) return;
for(int i = 0;i<candidates.size();i++)
{
t.push_back(candidates[i]);
helper(candidates,target,st,t,cur+candidates[i]);
t.pop_back();
}
}
};
2、《leetcode40——组合总和Ⅱ》
题解:该题与上一题唯一的区别就是每个数字在同一组合中只能用一次。那么我们只需在每次递归进入下一层的时候将位置设置成当前位置加一,而不是每次递归都从头开始遍历。又由于保证不重复相同的元素,那如果我们一开始就对candidates数组排好序会让后面的操作更方便,如果当前位与下一位相等,则直接i++。具体看代码实现。
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
vector<vector<int>> ans;
vector<int> t;
helper(candidates,target,ans,t,0,0);
return ans;
}
void helper(vector<int>& candidates,int target,vector<vector<int>>& ans,vector<int>& t,int pos,int cur)
{
if(cur == target)
{
ans.push_back(t);
return;
}
if(cur>target)
return;
for(int i = pos;i<candidates.size();i++)
{
t.push_back(candidates[i]);
helper(candidates,target,ans,t,i+1,cur+candidates[i]);
t.pop_back();
while(candidates[i] == candidates[i+1]) i++;
}
}
};
3、《leetcode216——组合总和Ⅲ》
题解:组合总和的第三题,跟上一题还是一样,只不过这次没明确给出数组,而是允许组合的数字为1-9,那么数组就是1-9啦。
做法几乎和40题一样,只不过这次不初始化一个cur=0的变量,而是直接让目标值n一边递归一边减掉当前的值,直到n==0并且有k个数。其实跟之前那样用加的做法也是可以的,只不过要定义多一个变量而已,看个人喜欢。具体实现看代码。
class Solution {
public:
vector<vector<int>> combinationSum3(int k, int n) {
vector<vector<int>> ans;
vector<int> t;
helper(k,n,ans,t,1);
return ans;
}
void helper(int k,int n,vector<vector<int>>& ans,vector<int>& t,int pos)
{
if(t.size() == k&&n==0)
{
ans.push_back(t);
return;
}
if(n<0) return;
for(int i = pos;i<=9;i++)
{
t.push_back(i);
helper(k,n-i,ans,t,i+1);
t.pop_back();
}
}
};
4、《leetcode79——单词搜索》
题解:一道比较简单的搜索题,从上下左右四个方向进行搜索,满足条件返回true。注意已经访问过的点不能再搜索并记得回溯即可。
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
if(board.empty()||board[0].empty()) return false;
int n = board.size(),m = board[0].size();
vector<vector<int>> vis(n,vector<int>(m,0)); //vis数组用来记录某个点是否访问过了
for(int i = 0;i<n;i++)
{
for(int j = 0;j<m;j++)
{
if(dfs(board,i,j,word,0,vis))
return true;
}
}
return false;
}
bool dfs(vector<vector<char>>& board,int x,int y,string word,int cur,vector<vector<int>>& vis)
{
if(cur == word.size())
return true;
int n = board.size(),m = board[0].size();
if(x<0||y<0||x>=n||y>=m||vis[x][y]||board[x][y]!=word[cur])
return false;
vis[x][y] = 1;
bool flag = dfs(board,x+1,y,word,cur+1,vis)||
dfs(board,x-1,y,word,cur+1,vis)||
dfs(board,x,y+1,word,cur+1,vis)||
dfs(board,x,y-1,word,cur+1,vis);
vis[x][y] = 0; //记得回溯
return flag;
}
};
1、《leetcode41——缺失的第一个正数》
题解:首先我们定一个规则:数字1在下标为0的位置,数字2在下标为1的位置。。。以此类推,那么数字n应该在下标为n-1的位置。有了这个规则后,我们开始遍历数组,当遇到一个正数不符合我们规则的情况就把该数字交换到符合我们规则的位置上。
遍历交换完成后再遍历一次数组,如果在某个位置遇到不符合规则的数字,则把这个位置本来应该符合的数字返回,也即我们的答案。如果在这第二次遍历结束都没问题则证明原数组全部符合规则了,那么最后返回n+1。
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
for(int i = 0;i<n;i++)
{
while(nums[i]>0&&nums[i]<=n&&nums[i]!=nums[nums[i]-1])
swap(nums[i],nums[nums[i]-1]);
}
for(int i = 0;i<n;i++)
{
if(nums[i]!=i+1)
return i+1;
}
return n+1;
}
};
2、《leetcode78——子集》
题解:假设数组长度为len,则一共有1<<len种子集。接下来只需按位运算,具体实现看代码。
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
if(nums.empty()) return res;
int len = nums.size();
int n = 1<<len;
for(int i = 0;i<n;i++)
{
vector<int> v;
for(int j = 0;j<len;j++)
{
if(i>>j&1)
v.push_back(nums[j]);
}
res.push_back(v);
}
return res;
}
};
3、《leetcode90——子集Ⅱ》
题解:与78题几乎一样的做法,因为不能含有重复的子集,这里用到set去重。
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
set<vector<int>> st;
int len = nums.size();
int n = 1<<len;
for(int i = 0;i<n;i++)
{
vector<int> t;
for(int j = 0;j<len;j++)
{
if(i>>j&1)
t.push_back(nums[j]);
}
sort(t.begin(),t.end());
st.insert(t);
}
return vector<vector<int>>(st.begin(),st.end());
}
};
4、《leetcode238——除自身以外数组的乘积》
题解:整体做法就是从头扫到尾,再从尾扫到头。第一次扫得到的结果为数组每个位置的数字都是从第一个元素到该位置前一个位置元素的乘积,例如a[1]=a[0],a[2] = a[1]*[0],a[3] = a[2]*a[1]*a[0]。。。以此类推。第二次扫目的是乘后面的数,也就得出最终的结果了。实现看代码。
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int len = nums.size();
vector<int> ans(len,1);
//第一次扫
for(int i = 1;i<len;i++)
ans[i] = ans[i-1]*nums[i-1];
int t = 1;
//第二次扫
for(int i = len-1;i>=0;i--)
{
ans[i]*=t;
t*=nums[i];
}
return ans;
}
};
5、《leetcode289——生命游戏》
题解:题目的进阶要求是使用原地算法,也就是不能另开一个新的矩阵来做。这里要用到点小技巧,题目说到1代表活细胞,0代表死细胞。现在我们重新定义:1和3代表活细胞,0和2代表死细胞。看到这里可能有小伙伴疑惑2和3是怎么得出来的,看完下面就明白,反正现在就先当是这样。然后再来看生存定律,不难得出:1、活细胞周围的活细胞少于2个或者多于3个会变成死细胞。2、死细胞周围刚好又3个活细胞则会变成活细胞。我们声明一个变量cnt来记录当前细胞周围有几个活细胞,记录完成后,检测当前细胞是活细胞还是死细胞,若是活细胞而cnt<2||cnt>3,则令当前的活细胞由1变成2;若是死细胞而cnt==3,则另当前的死细胞由0变成3。
看到这里应该知道 2和3怎么来的了吧。经过上面的操作后,最终矩阵里面数字代表的意义为1代表活细胞(从一开始到结束就没变过);3代表活细胞,是从死细胞变成的;0代表死细胞(也是从一开始到结束就没变过),2代表死细胞,是从活细胞变成的。这样做的意义在于,当0,1,2,3分别%2的时候变成0,1,0,1。发现只要是死细胞%2后都是0,活细胞%2后都是1。
需要注意的点是如果board[x][y]==2代表board[x][y]这个位置曾经是活细胞,虽然现在变成死细胞了,但我们在数数的时候依然要把它当成活细胞来数。具体实现看代码。
class Solution {
public:
void gameOfLife(vector<vector<int>>& board) {
int m = board.size(),n = m?board[0].size():0;
int _x[8] = {1,1,1,-1,-1,-1,0,0};
int _y[8] = {1,-1,0,-1,1,0,1,-1};
for(int i = 0;i<m;i++)
{
for(int j = 0;j<n;j++)
{
int cnt = 0;
for(int k = 0;k<8;k++)
{
int x = i+_x[k],y = j+_y[k];
//board[x][y]==2代表board[x][y]这个位置曾经是活细胞,现在变成死细胞了,但我们现在依然要当它是活细胞来进行数数
if(x>=0&&x<m&&y>=0&&y<n&&(board[x][y] == 1||board[x][y] == 2))
cnt++;
}
if(board[i][j])
{
if(cnt<2||cnt>3)
board[i][j] = 2;
}
else if(!board[i][j])
{
if(cnt == 3)
board[i][j] = 3;
}
}
}
for(int i = 0;i<m;i++)
{
for(int j = 0;j<n;j++)
board[i][j]%=2;
}
}
};
6、《leetcode11——盛最多水的容器》
题解:设置一头一尾指针,首先计算当前两个指针所指位置所能盛的最多水,然后让两个指针向中间靠拢。靠拢规则:哪个指针所指的数值小则哪个向中间靠。重复以上动作以不断更新结果。
class Solution {
public:
int maxArea(vector<int>& height) {
int left = 0,right = height.size()-1;
int ans = 0;
while(left<right)
{
int t = min(height[left],height[right])*(right-left);
ans = max(ans,t);
if(height[left]<height[right])
left++;
else right--;
}
return ans;
}
};
7、《leetcode16——最接近的三数之和》
题解:题目要求最接近目标值的三个数字之和,我们可以转化成求最接近目标值的两个数字之和。先令目标值target减去数组其中的一个数字x,得到的值赋给sum变量,剩下的问题就是求数组中除了x两个数之和最接近sum的问题。为了方便解题,我们先对数组升序排序。那么在求两个数之和最接近sum这个问题就可以用两个指针来做了,一个指向头,一个指向尾,然后不断往中间靠拢更近ans。
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int ans = 0;
int min = 0x3f3f3f3f;
sort(nums.begin(),nums.end());
for(int i = 0;i<nums.size()-2;i++)
{
int j = i+1,k = nums.size()-1;
int sum = target-nums[i];
while(j<k)
{
if(abs(sum-nums[j]-nums[k])<min)
{
min = abs(sum-nums[j]-nums[k]);
ans = nums[i]+nums[j]+nums[k];
}
if(nums[j]+nums[k]<sum)
j++;
else k--;
}
}
return ans;
}
};
8、《leetcode209——长度最小的子数组》
题解:把原问题拆成两步来解,第一步:找到和>=s的子数组;第二步:把第一步找到的子数组在满足第一步的前提下尽量缩小左右之间的范围,得到最小的那个范围即为答案。首先设置两个指针left和right,[left,right]区间的最小值就是我们的答案,先是right指针往后走,一边走一边把数组里的数字加起来保存在cur变量里,直到满足cur>=s为止;接着轮到left指针向后走,如果还能满足第一步的条件则更新[left,right]区间的最小值。
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int ans = 0x3f3f3f3f;
int left = 0,right = -1;
int cur = 0,len = nums.size();
if(len == 0) return 0;
while(right<len)
{
while(cur<s&&right<len)
cur+=nums[++right];
if(cur>=s)
{
ans = min(ans,right-left+1);
cur-=nums[left++];
}
}
return ans>len?0:ans;
}
};
9、《leetcode611——有效的三角形个数》
题解:解题主要利用了三角形的特性:任一两边之和一定大于第三条边。于是我们可以先对数组进行升序排列,从最大的数字往小的数字进行枚举,枚举的数字被当做三角形最大的那条边。接着定义两个指针一前一后往中间靠拢,期间如果满足三角形的特性则把两个指针的距离加起来。具体实现看代码。
class Solution {
public:
int triangleNumber(vector<int>& nums) {
sort(nums.begin(),nums.end());
int ans = 0;
for(int i = nums.size()-1;i>=2;i--)
{
int left = 0,right = i-1;
while(left<right)
{
if(nums[left]+nums[right]>nums[i])
{
ans += right-left;
right--;
}
else
left++;
}
}
return ans;
}
};
10、《leetcode769——最多能完成排序的块》
题解:发现个规律,断开的地方的下标刚好等于该下标之前最大的数的值。
例如[1,0,2,3,4],1的下标是0,1(包括1)之前最大的值是1,0!=1,不断;0的下标是1,0(包括0)之前最大的值是1,1==1,所以在0之后断[1,0];2的下标是2,之前最大的值是2,断开,[2];接下去3和4同理。
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
int len = arr.size();
int ans = 0,mx = 0;
for(int i = 0;i<len;i++)
{
mx = max(mx,arr[i]);
if(i == mx)
ans++;
}
return ans;
}
};
1、《P1101单词方阵》
题解:一共可以向八个方向走,题目主要的切入点是怎么做到一直沿同一个方向走,这里需要声明一个变量dir,它专门用来记录前一次是往哪个方向,那么这一次要根据前一次的方向往下走。其次这里还声明一个结构体,里面存的是一路走来的(x,y)坐标,相当于存放着历史路径。当我们深搜结束符合条件的时候可以根据结构体的历史路径对二维数组vis打标记。全部搜索工作完成后,根据vis数组的标记输出二维矩阵。具体实现看代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
struct NODE
{
int x, y;
}node[105];
char matrix[105][105];
int vis[105][105]; //vis用来打标记
//定义八个方向
int _move[8][2] = { {1,0},{0,1},{-1,0},{0,-1},{1,1},{1,-1},{-1,-1},{-1,1} };
char str[8] = "yizhong"; //我们的目标串
int n;
void dfs(int x, int y, int dir, int cur) //dir用作记录前一次走的方向
{
//符合条件
if (cur == 7)
{
for (int i = 0; i < 7; i++)
vis[node[i].x][node[i].y] = 1;
return;
}
if (matrix[x][y] == str[cur]) //当前位置符合,则把当前(x,y)坐标存进结构体,继续深搜
{
node[cur].x = x, node[cur].y = y;
dfs(x + _move[dir][0], y + _move[dir][1], dir, cur + 1);
}
}
int main()
{
ios::sync_with_stdio(false);
while (cin >> n)
{
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
cin >> matrix[i][j];
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (matrix[i][j] == 'y')
{
for (int k = 0; k < 8; k++) //开始遍历八个方向
{
int x = i + _move[k][0], y = j + _move[k][1];
if (matrix[x][y] == 'i')
{
node[0].x = i, node[0].y = j;
dfs(x, y, k, 1);
}
}
}
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (vis[i][j])
cout << matrix[i][j];
else cout << "*";
}
cout << endl;
}
}
system("pause");
return 0;
}
2、《P1141——01迷宫》
题解:一眼看过去跟一般的搜索题没什么两样,但是这道题按照常规的模拟去做的话会超时,这点一直恶心死我hhhh。为了避免超时,这题需要用到记忆化搜索,主要根据是:如果两个点是互通的,那么第一个点能到的全部地方第二个点也能到达,所以我们只要求一个点能到达的格子数,那么跟它相连的点就不必要做搜索工作了。具体实现看代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int n, m;
int vis[1005][1005];
char matrix[1005][1005];
int head, cnt; //head用来记录第几次搜索工作
int father[1005][1005]; //head[i][j] = k 意思是 坐标(i,j)能到达的格子数是在第k次搜索中求到。
int ans[10000005];
void dfs(int x, int y)
{
vis[x][y] = 1;
father[x][y] = head;
if (!vis[x + 1][y] && matrix[x + 1][y] != matrix[x][y] && x + 1 <= n)
{
cnt++;
dfs(x + 1, y);
}
if (!vis[x - 1][y] && matrix[x - 1][y] != matrix[x][y] && x - 1 > 0)
{
cnt++;
dfs(x - 1, y);
}
if (!vis[x][y + 1] && matrix[x][y + 1] != matrix[x][y] && y + 1 <= n)
{
cnt++;
dfs(x, y + 1);
}
if (!vis[x][y - 1] && matrix[x][y - 1] != matrix[x][y] && y - 1 > 0)
{
cnt++;
dfs(x, y - 1);
}
}
int main()
{
ios::sync_with_stdio(false);
while (cin >> n >> m)
{
memset(vis, 0, sizeof(vis));
head = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
cin >> matrix[i][j];
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (!vis[i][j])
{
cnt = 1;
head++;
dfs(i, j);
ans[head] = cnt;
}
}
}
int x, y;
while (m--)
{
cin >> x >> y;
cout << ans[father[x][y]] << endl;
}
}
system("pause");
return 0;
}
3、《P1434——滑雪》
题解:一个一个各搜索肯定会超时,遇到这种情况只能用记忆化搜索。开一个height[i][j]数组记录每个点(i,j)能滑到的最长距离。从当前点开始搜索,如果遇到之前已经搜素过的点则直接返回,反之进行搜索。具体实现看代码。
#include <iostream>
#include <queue>
#include <cmath>
#include <functional>
#include <algorithm>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <iomanip>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int r, c;
int a[105][105],height[105][105];
int _move[4][2] = { {1,0},{0,1},{-1,0},{0,-1} };
bool check(int x, int y)
{
if (x < 0 || x >= r || y < 0 || y >= c)
return false;
return true;
}
int dfs(int i, int j)
{
if (height[i][j]) return height[i][j]; //这个点之前搜索过
height[i][j] = 1; //没搜索过这个点,初始化为1
for (int k = 0; k < 4; k++) //四个方向进行搜索
{
int tx = i + _move[k][0], ty = j + _move[k][1];
if (check(tx, ty) && a[tx][ty] < a[i][j]) /下一个点没有越界并且下一个点的高度比当前点的高度低则继续搜索下去
height[i][j] = max(height[i][j], 1 + dfs(tx, ty));
}
return height[i][j];
}
int main()
{
ios::sync_with_stdio(false);
while (cin >> r >> c)
{
memset(height, 0, sizeof(height));
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
cin >> a[i][j];
}
int ans = 0;
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
ans = max(ans, dfs(i, j));
}
cout << ans << endl;
}
system("pause");
return 0;
}
4、《P1433——吃奶酪》
题解:首先计算每两点之间的距离并记录在dis数组中。然后一开始从0点出发,遍历其余点,遍历的时候一边加上两点之间的距离(之前在dis数组中已经存好两点间的距离)。遍历结束后换个方向遍历,取最小的ans。为了防止超时需要进行剪枝,剪枝的切入点是:当遍历的路程大于上次的ans时则停止遍历下去。
#include <iostream>
#include <queue>
#include <cmath>
#include <functional>
#include <algorithm>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <iomanip>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int n;
int vis[105];
double x[105], y[105], dis[105][105];
double ans;
void dfs(int step, int now, double length)
{
if (length > ans) return; //剪枝
if (step == n) //遍历完成
{
ans = min(ans, length);
return;
}
for (int i = 1; i <= n; i++) //一个一个点遍历下去
{
if (!vis[i])
{
vis[i] = 1;
dfs(step + 1, i, length + dis[now][i]);
vis[i] = 0;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
ans = 1231234424.0;
for (int i = 1; i <= n; i++)
cin >> x[i] >> y[i];
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= n; j++)
dis[i][j] = sqrt((x[i] - x[j])*(x[i] - x[j]) + (y[i] - y[j])*(y[i] - y[j]));
}
dfs(0, 0, 0.0); //从0点开始遍历
cout << fixed << setprecision(2) << ans << endl;
system("pause");
return 0;
}
1、《P1403——约数研究》
题解:纯暴力跑铁定超时,那么假设i是循环的因子,从1到n,j是i的倍数,由于是从i开始的,所以a[i]本身也加了一次,既然是i的倍数,那么就含有i这个因子,加1。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int a[1000005];
int solve(int n)
{
int ans = 0;
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j += i) //凡是i的倍数都+1
a[j]++;
ans += a[i];
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
int n;
while (cin >> n)
{
cout << solve(n) << endl;
}
system("pause");
return 0;
}
1、《hihocoder1081——最短路一(Dijkstra算法)》
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005;
const int INF = 0x7f7f7f7f;
int root[maxn][maxn],dis[maxn],vis[maxn];
int n,m,s,t;
void dijk(int s)
{
for(int i = 1;i<=n;i++)
dis[i] = root[s][i];
dis[s] = 0;
vis[s] = 1;
for(int i = 1;i<n;i++)
{
int min = INF,pos;
for(int j = 1;j<=n;j++)
{
if(!vis[j]&&dis[j]<min)
{
min = dis[j];
pos = j;
}
}
if(min == INF)
break;
vis[pos] = 1;
for(int j = 1;j<=n;j++)
{
if(!vis[j]&&dis[pos]+root[pos][j]<dis[j])
dis[j] = dis[pos]+root[pos][j];
}
}
}
int main()
{
while(cin>>n>>m>>s>>t)
{
memset(vis,0,sizeof(vis));
memset(root,0,sizeof(root));
for(int i = 0;i<maxn;i++)
{
for(int j = 0;j<maxn;j++)
{
if(i!=j)
root[i][j] = INF;
}
}
int u,v,len;
for(int i = 0;i<m;i++)
{
cin>>u>>v>>len;
root[u][v] = root[v][u] = min(root[u][v],len);
}
dijk(s);
cout<<dis[t]<<endl;
}
return 0;
}
2、《hihocoder1089——最短路二(Floyd算法)》
#include <iostream>
#include <queue>
#include <functional>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn = 110;
int mp[maxn][maxn];
int n, m;
int x, y, z;
void solve()
{
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (mp[i][k] != INF&&mp[k][j] != INF)
{
if (mp[i][j] > mp[i][k] + mp[k][j])
mp[i][j] = mp[i][k] + mp[k][j];
}
}
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
cout << mp[i][j] << " ";
cout << endl;
}
}
int main()
{
ios::sync_with_stdio(false);
while (cin >> n >> m)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i == j)
mp[i][j] = 0;
else mp[i][j] = INF;
}
}
while (m--)
{
cin >> x >> y >> z;
mp[x][y] = mp[y][x] = min(mp[x][y], z);
}
solve();
}
system("pause");
return 0;
}
3、《hihocoder1093——最短路三(SPFA算法)
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int INF = 0x7fffffff;
struct edge
{
int y, w;
edge(int _y, int _w):y(_y),w(_w){}
};
vector<edge> v[MAXN];
int dis[MAXN], n, m, s, t;
bool vis[MAXN];
void spfa()
{
for(int i=1; i<=n; ++i)
dis[i] = INF;
dis[s] = 0;
//memset(vis+1, 0, n);
queue<int> q;
q.push(s);
while(!q.empty())
{
int x = q.front();
q.pop();
int sz = v[x].size();
for(int i=0; i<sz; ++i)
{
int y = v[x][i].y, w = v[x][i].w;
if(dis[y]>dis[x]+w)
{
dis[y] = dis[x]+w;
q.push(y);
}
}
}
}
int main()
{
while(cin>>n>>m>>s>>t)
{
for(int i=1; i<=n; ++i) v[i].clear();
while(m--)
{
int x, y, w;
cin>>x>>y>>w;
v[x].push_back(edge(y, w));
v[y].push_back(edge(x, w));
}
spfa();
cout<<dis[t]<<endl;
}
return 0;
}
4、《hihocoder1097——最小生成树(Prim算法)》
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x7f7f7f7f;
const int maxn = 1005;
int road[maxn][maxn],vis[maxn],dis[maxn];
int n;
int main()
{
while(cin>>n)
{
memset(road,0,sizeof(road));
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
cin>>road[i][j];
}
}
int sum = 0;
vis[1] = 1;
for(int i = 1;i<=n;i++)
dis[i] = road[1][i];
for(int i = 1;i<n;i++)
{
int min = INF,pos;
for(int j = 1;j<=n;j++)
{
if(!vis[j]&&dis[j]<min)
{
min = dis[j];
pos = j;
}
}
sum += dis[pos];
vis[pos] = 1;
for(int j = 1;j<=n;j++)
{
if(!vis[j]&&road[pos][j]<dis[j])
dis[j] = road[pos][j];
}
}
cout<<sum<<endl;
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
const int M = 5e5+5;
int n,m,x,y;
vector<int> v[M];
int din[N];
void init()
{
for(int i = 0;i<N;i++)
v[i].clear();
memset(din,0,sizeof(din));
}
bool tuopu()
{
queue<int> q;
for(int i = 1;i<=n;i++)
if(din[i] == 0)
q.push(i);
int cnt = 0;
while(!q.empty())
{
int u = q.front();
q.pop();
++cnt;
int len = v[u].size();
for(int i = 0;i<len;i++)
{
int k = v[u][i];
--din[k];
if(din[k] == 0)
{
q.push(k);
}
}
}
if(cnt == n)
return true;
else return false;
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n>>m;
init();
for(int i = 0;i<m;i++)
{
cin>>x>>y;
v[x].push_back(y);
din[y]++;
}
bool judge = tuopu();
if(judge) cout<<"Correct"<<endl;
else cout<<"Wrong"<<endl;
}
return 0;
}