第一期算法专题概述
作为第一期算法训练,其难度并不难。但是作为正常的算法学习者而言。不应该被当下所束缚,所以这里会尽可能会说到更多的方法。作为对之前算法学习的回顾。这里用到的方法会在其他的文档中做出详细的描述。
0~n-1 缺失的数字
题目描述:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例一:
输入:[0,1,3]
输出:2
示例二:
输入:[0,1,2,3,4,5,6,7,9]
输出:8
限制:
1 <= 数组长度 <= 10000
思路:
一个一个找,直到nums[i] != i; 即可。
这里一个一个找也是有不同的方式的:
- 单纯的用if判别;
- 按位”异或“操作;
- 算出nums数组中所有数字的总和,然后一个一个的减。最后减完了,直接返回即可;
代码会在后面给出。
但是这里想说的思路是一个二分法的思路;
我们不想一个一个的找,而是利用有序这个天生的条件。使用二分法。
代码(代码来源于网络):
//逐位异或操作class Solution {public: int missingNumber(vector<int>& nums) { int res=nums.size(); for(int i=0;i<nums.size();i++) { res^=nums[i]; res^=i; } return res; }};//用sum一个一个的减class Solution {public: int missingNumber(vector<int>& nums) { int sum=(nums.size()*(nums.size()+1))/2; for(int i=0;i<nums.size();i++) { sum-=nums[i]; } return sum; }};//二分法int GetMissingNum(const int* num, int len){ if(num == NULL || len <= 0) return -1; int left = 0; //二分查找最左边元素下标 int right = len-1; //二分查找最右边元素下标 while (left <= right) //查找过程 { int mid = (left + right) >> 1; //中间元素下标 if(num[mid] != mid) //表示mid处或其左边有元素缺失 { //若mid == 0或mid前一个元素数字和下标相等,则mid即为所求 if(mid == 0 || num[mid-1] == mid - 1) return mid; right = mid - 1; //right减小,往左缩小范围,继续查找 } else //往右半部分查找 { left = mid + 1; } } if (left == len) //查找到最后一个元素,其即为所求 return len; return -1; //无效输入,返回-1}
最长连续递增序列
描述:给定一个未经排序的整数数组,找到最长且连续的的递增序列,并返回该序列的长度。
示例一:
输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。
示例二:
输入: [2,2,2,2,2]输出: 1解释: 最长连续递增序列是 [2], 长度为1。
限制:数组长度不会超过10000。
思路:
这里给出两个思路:
- 滑动窗口:挨个找每个元素的滑动窗口。
- 动态规划:之后专门文档讲解动态规划的种种。有关本题目的动态规划相对容易,不需要显示dp数组只需要给结果就行。
代码:
//双指针class Solution {public: // 滑动窗口 int findLengthOfLCIS(vector<int>& nums) { if(nums.empty()) return 0; int n=nums.size(),ans=0,ancher=0; for(int i=0;i<n;i++){ if(nums[i-1]>=nums[i]){ ancher=i; // 更改一次滑动窗口 }else{ ans=max(res,i-ancher+1); // 更改滑动窗口时,更新res } } return ans; }};//动态规划:class Solution {public: int findLengthOfLCIS(vector<int>& nums) { if(nums.empty()) return 0; int n = nums.size(), cnt = 1, ans = 1; if(n<=1) return n; for(int i=1;i<n;i++) { if(nums[i]>nums[i-1]) cnt++; else cnt = 1; ans = max(ans, cnt); } return ans; }};//双指针:class Solution {public: int findLengthOfLCIS(vector<int>& nums) { if(nums.empty()) return 0; int n=nums.size(),first=0,second,ans=1; for(second=1;second<n;) // 后面的那个指针遍历到最后 { int tem=1; if(nums[second]>nums[second-1]) // 固定一次前指针,得到一个有序的个数 { second++; tem=second-first; // 固定一次前指针,得到最大的个数 ans=max(ans,tem); } else // 当不在大于前一个的时候, { first=second; // 把慢指针指向当前数值 second++; // 快指针往前 ans=max(ans,tem); //比较当前最大值和之前答案取最大 tem=1; } } return ans; }};
合并两个有序数组
描述:
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
思路:
代码:
class Solution {public: void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) { int i = m - 1; //作为nums1数组的指针 int j = n - 1; //作为nums2数组的指针 int p = m + n - 1; //最终的nums1数组的位置指针 while(j >= 0 && i >= 0){ if(nums2[j] > nums1[i]){ nums1[p--] = nums2[j--]; } else{ nums1[p--] = nums1[i--]; } } while(i >= 0){ nums1[p--] = nums1[i--]; } while(j >= 0){ nums1[p--] = nums2[j--]; } }};
公交站之间的距离
描述:
环形公交路线上有 n 个站,按次序从 0 到 n - 1 进行编号。我们已知每一对相邻公交站之间的距离,distance[i] 表示编号为 i 的车站和编号为 (i + 1) % n 的车站之间的距离。
环线上的公交车都可以按顺时针和逆时针的方向行驶。
返回乘客从出发点 start 到目的地 destination 之间的最短距离。
示例 1:
输入:distance = [1,2,3,4], start = 0, destination = 1
输出:1
解释:公交站 0 和 1 之间的距离是 1 或 9,最小值是 1。
示例 2:
输入:distance = [1,2,3,4], start = 0, destination = 2
输出:3
解释:公交站 0 和 2 之间的距离是 3 或 7,最小值是 3。
示例 3:
~~~~
输入:distance = [1,2,3,4], start = 0, destination = 3
输出:4
解释:公交站 0 和 3 之间的距离是 6 或 4,最小值是 4。
限制:
- 1 <= n <= 10^4
- distance.length == n
- 0 <= start, destination < n
- 0 <= distance[i] <= 10^4
思路:
和补码的想法类似:sum是一定的,而言只有一个环,那么情况就是直接的距离d还是反绕一圈的距离sum-d哪一个更小。
代码:
class Solution {public: int distanceBetweenBusStops(vector<int>& distance, int start, int destination) { int sum = 0; int a = 0,i; for(int i = 0; i < distance.size(); i++) { sum+=distance[i]; } for(i = start>destination?destination:start ; i < (start>destination?start:destination); i++) { a += distance[i]; } return a>(sum-a)?(sum-a):a; }};
删除数组中的重复项
描述:
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 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。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
思路:
首先看说明,修改操作对数组调用者是可见的。这就意味的别做直接把元素拿出扔进set里面,再返回结果这种偷鸡摸狗的操作,乖乖自己写。
思路1:
其问题的核心思想是扫描和替换。仍然可以使用双指针操作。
思路2:
用index记录不重复数字的个数。
如果nums[i] != nums[i-1].则出现了新的不重复值,令nums[index] = nums[i]即可
值得注意的是:第一个数字nums[0]肯定要记录下来。所以判断条件是i-1不是i+1.而且i+1还要作数组的判断。
思路3:
在思路2里面当不相等时则判断有不重复数字。但是数字是递增的,所以只有后一个>前一个也是出现了新的不重复数字。其优势在于解决了思路2的特殊情况,即空数组情况。
代码:
//双指针(Java也能实现指针思路):public class Solution { public int replaceTwo(int[] nums){ if(nums.length <= 2) { return nums.length; } //1,1,1,2,2,3 int count = 1; //count为元素重复次数 int k = 1; //核心思想是替换,将元素重复次数大于2的部分忽略,将其他合法元素移动到前面合法区域。 //这里用i和k同时进行扫描,但是,当元素count不小于2时,(即两数相等次数达到2,也就是说相同元素达到3时) //这时,k会停下,直到i找到下一个与当前k-1指向当元素不相等的那一个坐标,才继续进行替换,重复所有操作。 for(int i = 1; i < nums.length; i++){ //如果相等,则重复次数加1 if(nums[i] == nums[i-1]) { if(count < 2) { //当重复达到两次时,才进行变换操作 nums[k++] = nums[i]; count++; } }else { //如果遇到两数不想等。则情况count次数 count = 1; nums[k++] = nums[i]; } } return k; } public static void main(String[] args) { Solution sl = new Solution(); int nums1[] = {0,0,1,1,1,2,3,3}; int nums2[] = {0,0,1,1,1,2,3,3}; int len1 = sl.replaceTwo(nums1); int len2 = sl.replaceTwo(nums2); for(int i = 0;i < len1; i++){ System.out.print(nums1[i] + " "); } System.out.println(); for(int j = 0;j < len2; j++){ System.out.print(nums2[j] + " "); } }}
//思路2class Solution {public: int removeDuplicates(vector<int>& nums) { int n = nums.size(); if (n == 0) { return 0; } int index = 0; // nums[0]不变,index从1开始赋值。 for (auto i = 1; i < n; ++ i) { if (nums[i] != nums[i - 1]) { index ++; nums[index] = nums[i]; } } return index + 1; }};//思路3class Solution {public: int removeDuplicates(vector<int>& nums) { int i = !nums.empty(); for (int n : nums) if (n > nums[i - 1]) nums[i ++] = n; return i; }};