算法学习笔记(刷题路线使用的是‘英雄哪里出来’给出的)

一.枚举

1.线性枚举

1)最值算法

        1464.数组中两元素的最大乘积

        给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j使 (nums[i]-1)*(nums[j]-1) 取得最大值。请你计算并返回该式的最大值。

        示例:输入:3 4 5 2

                   输出:12

                   解释:(4-1)*(5-1)=12

int maxProduct(int* nums, int numsSize) {
   int i,j,temp;
	for(i=0;i<numsSize;i++){
		for(j=i+1;j<numsSize;j++){
			if(nums[i]>nums[j]){
				temp=nums[i];
				nums[i]=nums[j];
				nums[j]=temp; 
			}
		}
	}
	return nums[numsSize-1]-1)*(nums[numsSize-2]-1;
}

分析:先对数组进行从小到大排序,就很容易找出最大值和次最大值。(O(N^2))

       

        485.最大连续1的个数

        给定一个二进制数组 nums(该数组的元素只能为0,1) , 计算其中最大连续 1 的个数。

        示例:输入:1 1 0 1 1 1

                   输出:3

int findMaxConsecutiveOnes(int* nums, int numsSize){
    int i,a=0,max=0;
    for(i=0;i<numsSize;i++){
        if(nums[i]==1){
            a++;
            if(a>max) max=a;
        }
        else a=0;
    }
    return max;
    
}

分析:先对整个数组进行遍历,寻找1,找到1了就用计数器加1,若碰到不为1(即0),则重置计数器。然后再重复上一步过程,找出数组中最大的连续1个数。(O(N))

        153.寻找旋转排序数组中的最小值

        已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

        注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

        给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
​
int findMin(int* nums, int numsSize){
    int left=0,right=numsSize-1;
    while(right>left){
        int mid=left+(right-left)/2;
        if(nums[mid]<nums[right]){
            right=mid;
        }
        else{
            left=mid+1;
        }
    }
    return nums[left];
}

​

分析:利用二分查找找到旋转数组中的最小值(该数组旋转前有序)

第一行:定义left并初始化为0,right为numsSize-1(因为数组下标为从0开始)

第二行:利用while循环,当right等于left时跳出循环

第三行:取中间值mid(之所以用left+(right-left)/2,是为了防止两个int类型相加溢出)

第四行:当中间值小于最右边的值时,说明中间到最右边这个区间无最小值

第五行:将最右边移到中间(mid不加1的原因:因为中间值小于最右边的值,所以无法保证中间值不是最小值)

第七行:当中间值大于最右边的值时,说明中间到最右边这个区间有最小值

第八行:将最左边移到中间(mid+1的原因:因为中间值大于最右边的值,所以中间值一定不是最小值)

重复三四五七八行的操作,直至跳出while循环,最后返回nums[left]

        154.寻找旋转排序数组中的最小值II

        已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
  • 若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]

        注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

        给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。你必须尽可能减少整个过程的操作步骤。

示例 1:

输入:nums = [1,3,5]
输出:1

示例 2:

输入:nums = [2,2,2,0,1]
输出:0

int findMin(int* nums, int numsSize) {
    int left=0;
    int right=numsSize-1;
    while(right>left){
        int mid=left+(right-left)/2;
        if(nums[mid]>nums[right]) left=mid+1;
        else if (nums[mid]<nums[right]) right=mid;
        else right--;
    }
    return nums[left];
}

分析:同153,就是多出一个条件:数组中的元素可以重复,所以拎出来中间值等于最右边值

        414.第三大的数

        给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。

示例 1:

输入:[3, 2, 1]
输出:1
解释:第三大的数是 1 。

示例 2:

输入:[1, 2]
输出:2
解释:第三大的数不存在, 所以返回最大的数 2 。

示例 3:

输入:[2, 2, 3, 1]
输出:1
解释:注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。
此例中存在两个值为 2 的数,它们都排第二。在所有不同数字中第三大的数为 1 。
int cmp(int *a,int *b){
    return *b>*a;
}
int thirdMax(int* nums, int numsSize){
    qsort(nums,numsSize,sizeof(int),cmp);
    int k=0;
    for(int i=1;i<numsSize;i++){
        if(nums[i]!=nums[i-1]&&++k==2) return nums[i];
    }
    return nums[0];
}

​

分析:引用了cmp对数组进行排序,然后寻找第三大的数    O(N)

        121.买卖股票的最佳时机

        给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
int maxProfit(int* prices, int pricesSize) {
    int i,j;
    int max=0;
    for(i=0,j=0;j<pricesSize;j++){
        if(prices[j]<prices[i]) i=j;
        else if(prices[j]-prices[i]>max) max=prices[j]-prices[i];
    }
    return max;
}
分析:这里利用了双指针,感觉这个方法不错,就是需要稍微思考一下。

简单分析一下,当prices[i]>prices[j]时,将下标值j赋值给i,这样就完成了整体向前移动一个数。如果不好理解,可以用示例对照代码理解。

比如示例1:7 1 5 3 6 4

因为7>1(即prices[0]>prices[1]),所以i变成了1,j自增1(即j=2,i=1).

又1<5,所以执行else if里的条件,5-1=4,此时max记录第一个值4

后边过程同上,如果前大于后那就i变成j,j自增1。如果后大于前那就计算max并记录,直到找出最大利润值。 时间复杂度:O(N)

当然还有其他方法,比如直接遍历数组寻找最小值,再遍历数组逐个减最小值,找出最大值。以及最暴力两个for循环(太慢了,时间复杂度为O(N^2))。还有一种就是动态规划(博主当前还在学习,学成归来补充)。

        628.三个数的最大乘积

        给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

示例 1:

输入:nums = [1,2,3]
输出:6

示例 2:

输入:nums = [1,2,3,4]
输出:24

示例 3:

输入:nums = [-1,-2,-3]
输出:-6
int cmp(int *a,int *b){
    return *a-*b;
}
int maximumProduct(int* nums, int numsSize) {
    qsort(nums,numsSize,sizeof(int),cmp);
    return fmax(nums[0]*nums[1]*nums[numsSize-1],nums[numsSize-1]*nums[numsSize-2]*nums[numsSize-3]);
}

分析:这里新学了个cmp,用来排序的,能够减少运算时间,提高效率。

这题不难,主要的就是注意两种情况:

第一,有负数时,我们要选最小的前两个值(负的,最小的负和负,不就是最大的正嘛,哈哈)然后再选最大的值。

第二,没有负数时,直接选最大的前三个数就行了。

总结:以上就是所有最值算法的内容,总体刷下来收获了很多很多知识:比如,二分查找、cmp的使用、双指针的简单应用、动态规划(还在学习)等等,接一下来就是原地算法,继续加油。

2)原地算法

        26.删除有序数组中的重复项

        给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k 。

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有断言都通过,那么您的题解将被 通过

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2]

示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
int removeDuplicates(int* nums, int numsSize) {
    int i=1;
    for(int j=1;j<numsSize;j++){
        if(nums[j]!=nums[j-1]){
            nums[i]=nums[j];
            i++;
        }
    }
    return i;
}

分析:还是利用双指针,先确定指针指的初始值,这里指的为数组中的第二个数(即nums[1])。

当j指向的值不等于j-1指向的值时,将j指向的值复制给i指向的值,然后i和j同时+1。

当j指向的值等于j-1指向的值时,j+1,i仍是原值。

直至j遍历到最后一个值,然后返回i的值(即数组的长度)

时间复杂度:O(N)

还可以用while来做这个题:

int removeDuplicates(int* nums, int numsSize) {
    int i=1,j=1;
    while(j<numsSize){
        if(nums[j]!=nums[j-1]){
            nums[i]=nums[j];
            i++;
        }
        j++;
    }
    return i;
}

分析:思路和上边的for一样。

        217.存在重复元素

        给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。

示例 1:

输入:nums = [1,2,3,1]
输出:true

示例 2:

输入:nums = [1,2,3,4]
输出:false

示例 3:

输入:nums = [1,1,1,3,3,4,3,2,4,2]
输出:true

方法一:

int cmp(int *a,int *b){
    return *a-*b;
}
bool containsDuplicate(int* nums, int numsSize) {
    qsort(nums,numsSize,sizeof(int),cmp);
    for(int i=1;i<numsSize;i++){
        if(nums[i-1]==nums[i]) return true;
    }
    return false;
}

分析:还是利用之前的cmp进行数组从小到大排序。对排序后的数组进行遍历,如果发现前后两个数相等,则返回true(即有重复数)

方法二:

struct hashTable{
    int key;
    UT_hash_handle hh;
};
bool containsDuplicate(int* nums, int numsSize) {
    struct hashTable *set=NULL;
    for(int i=0;i<numsSize;i++){
        struct hashTable *tmp;
        HASH_FIND_INT(set,nums+i,tmp);
        if(tmp==NULL){
            tmp=malloc(sizeof(struct hashTable));
            (*tmp).key=nums[i];
            HASH_ADD_INT(set,key,tmp);
        }
        else return true;
    }
    return false;
}

分析:该方法运用了哈希表。

        27.移除元素

        给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
int removeElement(int* nums, int numsSize, int val) {
    int i=0,j=numsSize;
    while(i<j){
        if(nums[i]==val){
            nums[i]=nums[j-1];
            j--;
        }
        else i++;
    }
    return i;
}

分析:初始化i=0,j=numsSize。遍历数组,从i开始

当nums[i]等于我们所给的值时,将nums[j-1]赋值给nums[i](为什么时j-1呢,因为数组下标是从0开始的,numsSize是数组的长度),然后j向左移。

不相等是,i向右移,直到退出while循环

最后返回i

也就是说如果发现我们指定的值,就拿后边的值覆盖前边的值。

如果后边的值就是我们的指定值也没事,继续上边的操作,知道后边的值不是我们指定的值。

还有一种方法用for:

int removeElement(int* nums, int numsSize, int val) {
    int i=0;
    for(int j=0;j<numsSize;j++){
        if(nums[j]!=val){
            nums[i]=nums[j];
            i++;
        }
    }
    return i;
}

这个也很好理解,我们初始化i和j都为0,然后开始遍历数组。

当我们的j指向的值不等于我们指定的值时,那就把j指向的值赋值给i指向的值,然后i和j同时+1

等于时,那j自己+1.

也就是说啊,当我们的j找到了我们指定的这个值,那就把这个值给i。没找到的话就和i一起往前走,直至找到这个指定值。

3)高精度

        66.加一

        给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。

示例 2:

输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。

示例 3:

输入:digits = [0]
输出:[1]
int* plusOne(int* digits, int digitsSize, int* returnSize) {
    for(int i=digitsSize-1;i>=0;i--){
        digits[i]+=1;
        if(digits%10!=0){
            *returnSize=digitsSize;
            return digits;
        }
        if(digits%10==0){
            digits[i]=0;
        }
    }
    int *ans=malloc(sizeof(int)*(digitsSize+1));
    memset=(ans,0,sizeof(int)*(digitsSize+1));
    ans[0]=1;
    *returnSize=digitsSize+1;
    return ans;
}

分析:这题需要注意的点就是9、109、199、999。也就是逢十进一嘛。

个位数不是9时,非常简单我们直接让末位数+1返回数组就行了。

那么当个位数为9时呢,那就个位数加完1以后,令末位数为0,是为进一就行。

同理十位和个位都为9那就,都加一并赋值为0,然后百位进一。

那如果每位上都是9呢,那就要在数组首位前边再开辟一个位置,所以我们引入新数组ans,并其值都设为0和开辟原digits数组长度+1个位置。

4)其他

        540.有序数组中的单一元素

        给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。请你找出并返回只出现一次的那个数。你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。

示例 1:

输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2

示例 2:

输入: nums =  [3,3,7,7,10,11,11]
输出: 10
int singleNonDuplicate(int* nums, int numsSize) {
    int hash[1000000];//尽量大一点,不然测试点过不了
    int i,a;
    memset(hash,a,sizeof(hash))
    for(i=0;i<numsSize;i++){
        ++hash[nums[i]];
    }
    for(i=0;i<numsSize;i++){
        if(hash[nums[i]]==1) a=nums[i];
    }
    return a;
}

分析:建立哈希表,将数组中的元素存储进去,如果存储的某个元素数量为1那就返回这个元素。

  • 36
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Likin Yao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值