第一周刷题总结
#leetcode
#动态规划
本周所做的动态规划类型题目大多是在一维数组、二维矩阵中的简单动态规划。
*一般解决动态规划问题,分为四个步骤,分别是
①问题拆解,找到问题之间的具体联系
②状态定义,例如,dp[i]表示当前第i步的最优解
③递推方程推导,找出状态方程,dp[i]与前面最优解之间的关系,如dp[i]=dp[i-1]+dp[i-2]
④实现,返回结果需要的解
一、70.爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 :
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1 阶 + 1 阶 + 1 阶
1 阶 + 2 阶
2 阶 + 1 阶
int climbStairs(int n){
if(n<=2)
return n;
int dp[n+1],i;
dp[0] = 0;dp[1] = 1;dp[2] = 2;
for(i=3;i<=n;i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
二、338.比特位计数
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例:
输入: 5
输出: [0,1,1,2,1,2]
/*
首先找到二进制进位之间的规律
0 1 2 3 4 5 6 7 8 9 10 11 12
0 1 10 11 100 101 110 111 1000 1001 1010 1011 1100
0 1 1 2 1 2 2 3 1 2 2 3 2
*/
int* countBits(int num, int* returnSize){
if(num==0)
*returnSize =1;
else
*returnSize = num+1;
int *res = malloc(sizeof(int)*(*returnSize));
res[0] = 0;
for(int i=1;i<=num;i++){
if(i%2==0){ //i为偶数:相当于i/2整体向左移动一位
res[i] = res[i/2];
}else{
res[i] = res[i-1] + 1; //i为奇数:前一位i-1加上1
}
}
return res;
}
三、62.不同路径
在一个m*n的网格中,每次只能向下或向右移动一格,从左上角到右下角一共有多少不同的路径
示例 :
输入: m = 3, n = 2
输出: 3
int uniquePaths(int m, int n){
if(m<=0 || n<=0)
return 0;
int dp[m][n],i,j;
for(i=0;i<m;i++){ //首先初始化第一行、第一列为1,这些格子只能由上一个格子通过一种走法(向下或向右)到达
dp[i][0]=1;
}
for(j=0;j<n;j++){
dp[0][j]=1;
}
for(i=1;i<m;i++){
for(j=1;j<n;j++){
dp[i][j] = dp[i-1][j] + dp[i][j-1]; //状态转移方程,dp[i][j]表示到达(i,j)格子的所有不同路径数
}
}
return dp[m-1][n-1]; //返回到达右下角格子的不同路径数
}
四、最小路径和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
示例 :
输入:grid = [
[1,3,1],
[1,5,1],
[4,2,1]
]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
#define min(x,y) ( x<y?x:y )
int minPathSum(int** grid, int gridSize, int* gridColSize){
if(gridSize<=0 || gridColSize<=0){
return 0;
}
int dp[gridSize][*gridColSize];
int i,j;
dp[0][0] = grid[0][0];
for(i=1;i<gridSize;i++){
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for(j=1;j<*gridColSize;j++){
dp[0][j] = dp[0][j-1] + grid[0][j];
}
for(i=1;i<gridSize;i++){
for(j=1;j<*gridColSize;j++){
dp[i][j] = min(dp[i-1][j] , dp[i][j-1]) + grid[i][j];
}
}
return dp[gridSize-1][*gridColSize-1];
}
五、55 .跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以永远不可能到达最后一个位置。
#define max(x,y) ( x>y?x:y )
bool canJump(int* nums, int numsSize) {
int k = 0; //k表示此时可以到达的最大索引位置
for (int i = 0; i < numsSize; i++){
if (i > k) return false;
k = max(k, i + nums[i]); //如果此时索引位置+可跳跃步数大于k,更新k
}
return true;
}
六、198.打家劫舍
一个小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响偷窃的唯一制约因素就是如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 :
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
#define max(x,y) ( x>y?x:y )
int rob(int* nums, int numsSize){
if(numsSize==0)
return 0;
if(numsSize==1)
return nums[0];
int i,dp[numsSize+1];
dp[0] = nums[0];
dp[1] = nums[0]>nums[1] ? nums[0] : nums[1];
for(i=2;i<numsSize;i++){
dp[i] = max(dp[i-1],dp[i-2]+nums[i]);//相当于在dp[i-2]、dp[i-1]、dp[i-2]+nums[i]中选择一个最大的作为dp[i]
}
return dp[numsSize-1];
}
七、650.只有两个键的键盘
开始在一个记事本上只有一个字符 ‘A’。每次可以对这个记事本进行两种操作:
Copy All (复制全部) ,Paste (粘贴)
给定一个数字 n 。输出能够打印出 n 个 ‘A’ 的最少操作次数。
示例 :
输入: 3
输出: 3
解释:
最初, 我们只有一个字符 ‘A’。
第 1 步, 我们使用 Copy All 操作。
第 2 步, 我们使用 Paste 操作来获得 ‘AA’。
第 3 步, 我们使用 Paste 操作来获得 ‘AAA’。
/*
首先找出一定的规律:(要获得i个A,可以由i的最大因子的最小操作数+i除以其最大因子的倍数)
1 2 3 4 5 6 7 8 9 10 ... 16 ... 32
0 2 3 4 5 5 7 6 6 7 8 10
*/
int minSteps(int n){
if(n==1)
return 0;
int dp[n+1],num;
dp[0]=-1;
dp[1]=0;
for(int i=2;i<=n;i++){
num = maxdiv(i);
dp[i] = dp[num] + i/num;
}
return dp[n];
}
int maxdiv(int x){//此函数用于获取一个数的最大因子,例如24->12,39->13
int i;
for(i=x/2;i>0;i--){
if(x%i==0){
break;
}
}
return i;
}
八、120.三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
#define min(x,y) ( x<y?x:y )
int minimumTotal(int** triangle, int triangleSize, int* triangleColSize){
if(triangleSize==1)
return triangle[0][0];
int i,j;
int **dp=(int**)malloc(sizeof(int*)*triangleSize); //定义dp数组的行数
for(i=0;i<triangleSize;i++){
dp[i]=(int*)malloc(sizeof(int)*triangleColSize[i]);//定义dp数组每一行的列数
}
dp[0][0]=triangle[0][0];
dp[1][0]=dp[0][0]+triangle[1][0];
dp[1][1]=dp[0][0]+triangle[1][1];
for(i=2;i<triangleSize;i++){
for(j=0;j<triangleColSize[i];j++){
if(j==0){
dp[i][j] = dp[i-1][j] + triangle[i][j]; //对于第一列的数组
}else if(j==i){
dp[i][j] = dp[i-1][j-1] + triangle[i][j]; //对于斜边上的数组
}else{
dp[i][j] = min(dp[i-1][j-1],dp[i-1][j]) + triangle[i][j];
}
}
}
i=i-1;
int res=dp[i][0];
for(j=1;j<triangleColSize[i];j++){ //遍历最后一行的数组,找出路径和最小的值
if(res>dp[i][j]) res=dp[i][j];
}
return res;
}
九、645.错误的集合
集合 S 包含从1到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复。给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。
示例 :
输入: nums = [1,2,2,4]
输出: [2,3]
*此题使用了哈希表的思想,整数1~n为哈希表中的键,其出现的次数为哈希表中的值
int* findErrorNums(int* nums, int numsSize, int* returnSize){
int *res = (int *)malloc(sizeof(int) * 2);
int *hash =(int *)malloc(sizeof(int )* (numsSize+1));
memset(hash,0,sizeof(int )* (numsSize+1));
int i;
for(i=0;i<numsSize;i++){//进行一次遍历,得到所有的键值对
hash[nums[i]] ++;
}
for(i=1;i<numsSize+1;i++){
if(hash[i]==0){//值为0的键表示丢失的整数
res[1] = i;
}
if(hash[i] == 2){//值为2的键表示重复的键
res[0] = i;
}
}
*returnSize = 2;
return res;
}
十、41.缺失的第一个正数
给你一个未排序的整数数组,默认没有重复的数,请找出其中没有出现的最小的正整数。
示例 1:
输入: [3,4,-1,1]
输出: 2
示例 2:
输入: [7,8,9,11,12]
输出: 1
*由题意可以知道一些隐含条件,只要产生缺失,最后的结果一定是在1~numSize之间,首先可以遍历一遍数组,将1到numSize之间的值,放在对应的数组下标为nums[i]-1位置下
int firstMissingPositive(int* nums, int numsSize){
int i,temp;
for(i=0;i<numsSize;i++){
//当num[i]在1~numSize之间并且没有位于正确的位置,进行交换
while ((nums[i] >= 1 && nums[i] <= numsSize) && (nums[i] != nums[nums[i]-1])){
int tmp = nums[i];
nums[i] = nums[tmp-1];
nums[tmp-1] = tmp;
}
}
for(i=0;i<numsSize;i++){//再次遍历数组,找到第一个键值不对应的值
if(nums[i]!=(i+1)){
return i+1;
break;
}
}
return numsSize+1;//如果都对应,表示没有产生缺失,返回numsSize+1
}
十一、274.H指数
h 指数的定义:h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。且其余的 N - h 篇论文每篇被引用次数 不超过 h 次。
例如:某人的 h 指数是 20,这表示他已发表的论文中,每篇被引用了至少 20 次的论文总共有 20 篇。
示例:
输入:citations = [3,0,6,1,5]
输出:3
解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3。
*由题意可以得到一些隐含条件,最后的结果一定是在1~citationsSize之间
int hIndex(int* citations, int citationsSize){
int i,*hash = (int*)calloc(citationsSize+1,sizeof(int));//cmolloc可以默认将数组赋值0
for(i=0;i<citationsSize;i++){
if(citations[i]>=citationsSize){//当键大于等于citationsSize,均相当于citationsSize的值加一
hash[citationsSize]++;
}else if(citations[i]>0){ //当键在1~citationsSize之间,相应键的值加一
hash[citations[i]]++;
}
}
if(hash[citationsSize]==citationsSize){
return citationsSize;
}
for(i=citationsSize-1;i>=0;i--){//对键从大到小遍历,并且逐步叠加,找出第一个得到的引用次数大于等于键的,然后返回引用次数
hash[i] += hash[i+1];
if(hash[i]>=i){
return i;
}
}
return 0;//没有找到则返回0
}
十二、697.数组的度
给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。
示例 :
输入: [1, 2, 2, 3, 1]
输出: 2
解释:
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.
int findShortestSubArray(int* nums, int numsSize){
int times[50000]={0},start[50000]={0},end[50000]={0}; //用times存放每个数的度
int i,count = 0,min = 50000; //start表示某个数首次出现的位置,end表示其最后出现的位置
for(i=0;i<numsSize;i++){
times[nums[i]]++;
if(times[nums[i]] > count)
count = times[nums[i]]; //count存放最大的度
if(times[nums[i]]==1){
start[nums[i]] = i;
end[nums[i]] = i;
}else if(times[nums[i]]>1){
end[nums[i]] = i;
}
}
for(i=0;i<50000;i++){ //在所有度数最大的值中,找出跨度最小的值,并返回跨度+1
if(times[i] == count){
if(end[i]-start[i]<min){
min = end[i]-start[i];
}
}
}
return min+1;
}
十三、300.最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
int lengthOfLIS(int* nums, int numsSize){
if(numsSize==0)
return numsSize;
int dp[numsSize],i,j,max=1;
for(i=0;i<numsSize;i++)//将dp数组全部初始化为1
dp[i]=1;
for(i=1;i<numsSize;i++){
for(j=0;j<i;j++){
if(nums[j]<nums[i] && dp[j]+1>dp[i]){
dp[i] = dp[j] + 1;
if(dp[i]>max){
max = dp[i];
}
}
}
}
return max;
}