算法部分
整数反转
原题
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例 1:
输入:x = 123
输出:321
示例 2:输入:x = -123
输出:-321
示例 3:输入:x = 120
输出:21
示例 4:输入:x = 0
输出:0
提示:
-231 <= x <= 231 - 1
思路
需要理清思绪,本题考查了两个关键点:
1.如何整数反转:数据用10取余后除以10,非0情况一直如此循环下去,可反向依次拿出每个数字。反向取数字过程中,结果依次乘以10,然后加当前位数字,循环结束可获得反转之后的数据。
2.还需要额外注意 -231 <= x <= 231 - 1
代码实例
int reverseLoop(int x) {
if(x == 0){
return 0;
}//判断x是否为0
int res = 0;
int tmp=0;
//当x不等于0时,对x用10取余后除以10
while (x != 0) {
tmp=x%10;
x/=10;
//控制x的范围
if(res>INT_MAX/10 ||(res==INT_MAX/10 && (tmp)>7) ){
return 0;
}
if (res<INT_MIN/10 || (res==INT_MIN/10 && (tmp)<-8) ){
return 0;
}
//对x进行整数反转
res = res*10+(tmp);
tmp=0;
}
return res;
}
简化版
int reverse(int x) {
int rev = 0;
while (x != 0) {
if (rev < INT_MIN / 10 || rev > INT_MAX / 10) {
return 0;
}
int digit = x % 10;
x /= 10;
rev = rev * 10 + digit;
}
return rev;
}
加一
原题
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 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]
提示:
1 <= digits.length <= 100
0 <= digits[i] <= 9
思路
需要考虑多种情况:
常规情况:找到最后一个数组元素加一,最后一个数组范围为0-8
特殊情况:当最后的数是9,如99,999等,这样+1,需要增加数组长度,将其变成100,1000等
- 如果 digits 的末尾有若干个 999,例如 [1,2,3,9,9][1, 2, 3, 9, 9][1,2,3,9,9],那么我们只需要找出从末尾开始的第一个不为 999 的元素,即 333,将该元素加一,得到 [1,2,4,9,9][1, 2, 4, 9, 9][1,2,4,9,9]。随后将末尾的 999 全部置零,得到 [1,2,4,0,0][1, 2, 4, 0, 0][1,2,4,0,0] 并返回。
- 如果digits 的所有元素都是 999,例如 [9,9,9,9,9][9, 9, 9, 9, 9][9,9,9,9,9],那么答案为 [1,0,0,0,0,0][1, 0, 0, 0, 0, 0][1,0,0,0,0,0]。我们只需要构造一个长度比digits 多 111 的新数组,将首元素置为 111,其余元素置为 000 即可。
代码实例
int* plusOne(int* digits, int digitsSize, int* returnSize)
{
int i=digitsSize-1; //数组最后一位
for(i;i>=0;i--)
{
// 如果当前位原数字为0-8,直接+1即可返回
if(digits[i]<9)
{
digits[i]++;
*returnSize=digitsSize;
return digits;
}
// 如果当前位为9,+1=10,所以当前位变为0
else
digits[i]=0;
}
// 程序走到这里,最高位产生进位,数组长度需要+1,增加数组长度
int* res=(int*)malloc(sizeof(int)*(digitsSize+1));
// 最高位变为1
res[0] = 1;
// 拷贝后面的数字到新的数组
for(i=0;i<digitsSize;i++)
{
res[i+1]=digits[i];
}
*returnSize=digitsSize+1;
return res;
}
简化版
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* plusOne(int* digits, int digitsSize, int* returnSize){
for(int i=digitsSize-1; i>=0; i--){
digits[i]++;
if(digits[i] < 10){
*returnSize = digitsSize;
return digits;
}
digits[i] = 0;
}
int* result = (int*)malloc(sizeof(int)*(digitsSize+1));
result[0] = 1;
for(int i=0; i<digitsSize; i++){
result[i+1] = digits[i];
}
*returnSize = digitsSize+1;
return result;
}
两数之和
原题
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
解题方案一:暴力枚举法
遍历每个元素 xx,并查找是否存在一个值与 target - xtarget−x 相等的目标元素。
算法复杂度分析:
时间复杂度:O(n^2),空间复杂度:O(1)
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
for (int i = 0; i < numsSize; ++i) {
for (int j = i + 1; j < numsSize; ++j) {
if (nums[i] + nums[j] == target) { //逐个寻求是否等于目标值
int* ret = malloc(sizeof(int) * 2); //创建一个数组
ret[0] = i, ret[1] = j;
*returnSize = 2;
return ret;
}
}
}
*returnSize = 0;
return NULL;
}
解题方案二: 两遍哈希表
注意到方法一的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。
使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)。
这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。
算法复杂度分析:
时间复杂度:O(N),其中 N是数组中的元素数量。对于每一个元素 x,我们可以 O(1) 地寻找 target - x。
空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。
//定义哈希表结构
struct hashTable {
int key;
int val;
UT_hash_handle hh;
};
struct hashTable* hashtable;
struct hashTable* find(int ikey) {
struct hashTable* tmp;
HASH_FIND_INT(hashtable, &ikey, tmp);
return tmp;
}
//插入哈希表
void insert(int ikey, int ival) {
struct hashTable* it = find(ikey);
if (it == NULL) {
struct hashTable* tmp = malloc(sizeof(struct hashTable));
tmp->key = ikey, tmp->val = ival;
HASH_ADD_INT(hashtable, key, tmp);
} else {
it->val = ival;
}
}
//利用哈希表,寻找target-x
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
hashtable = NULL;
for (int i = 0; i < numsSize; i++) {
struct hashTable* it = find(target - nums[i]);
if (it != NULL) {
int* ret = malloc(sizeof(int) * 2);
ret[0] = it->val, ret[1] = i;
*returnSize = 2;
return ret;
}
insert(nums[i], i);
}
*returnSize = 0;
return NULL;
}
所有奇数长度子数组的和
原题
给你一个正整数数组 arr ,请你计算所有可能的奇数长度子数组的和。
子数组 定义为原数组中的一个连续子序列。
请你返回 arr 中 所有奇数长度子数组的和 。
示例 1:
输入:arr = [1,4,2,5,3]
输出:58
解释:所有奇数长度子数组和它们的和为:
[1] = 1
[4] = 4
[2] = 2
[5] = 5
[3] = 3
[1,4,2] = 7
[4,2,5] = 11
[2,5,3] = 10
[1,4,2,5,3] = 15
我们将所有值求和得到 1 + 4 + 2 + 5 + 3 + 7 + 11 + 10 + 15 = 58
示例 2:输入:arr = [1,2]
输出:3
解释:总共只有 2 个长度为奇数的子数组,[1] 和 [2]。它们的和为 3 。
示例 3:输入:arr = [10,11,12]
输出:66
提示:
1 <= arr.length <= 100
1 <= arr[i] <= 1000
思路
可以组成多少个奇数数组
可以首先从头到尾遍历一遍,然后累加 每个数字的值乘以每个数字出现在子数组的次数 就可以得到结果。
接下来就变成我们是否可以找到每个数字出现在子数组中的次数规律。
- 假设我们选定一个数字,那么除了这个数字之外,我们还可以另外选择 偶数个其他数字 凑成奇数长度的子数组。
- 我们可以把其他数字的选择,分成左面和右面两部分,那么这个选定的数字出现在子数组的次数就是 左面可选数字方案数 * 右面数字可选方案数。
- 奇数=1 + 偶数,选定一个数字之后,如果左面选择奇数个数字,那么右面也必须选择奇数个数字;如果左面选择偶数个数字,那么右面也要选择偶数个数字。这样加上选定的数字,就刚好是奇数长度子数组。
- 最终每个数字出现的次数就是 左面选择奇数个数字的方案数 * 右面选择奇数个数字的方案数 + 左面选择偶数个数字的方案数 * 右面选择偶数个数字的方案数。
- 从0到n中,奇数的数量是(n + 1) / 2个,偶数的数量是n / 2 + 1(0也算偶数个)。
代码实例
int sumOddLengthSubarrays(int* arr, int arrSize){
int ans = 0;
int endIndex = arrSize - 1;
//遍历循环查找
for (int i = 0; i <= endIndex; i++) {
int leftCount = i;
int rightCount = endIndex - i;
//从0到n中,奇数的数量是(n + 1) / 2个,偶数的数量是n / 2 + 1(0也算偶数个)
int leftOdd = (leftCount + 1) / 2;
int rightOdd = (rightCount + 1) / 2;
int leftEven = leftCount / 2 + 1;
int rightEven = rightCount / 2 + 1;
/*数组中的数字*他们出现的次数,即乘
左面选择奇数个数字的方案数 * 右面选择奇数个数字的方案数
+左面选择偶数个数字的方案数 * 右面选择偶数个数字的方案数。
*/
ans += arr[i] * (leftOdd * rightOdd + leftEven * rightEven);
}
return ans;
}