一.枚举
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那就返回这个元素。