算法训练第一期

6次阅读

共计 5655 个字符,预计需要花费 15 分钟才能阅读完成。

第一期算法专题概述

作为第一期算法训练,其难度并不难。但是作为正常的算法学习者而言。不应该被当下所束缚,所以这里会尽可能会说到更多的方法。作为对之前算法学习的回顾。这里用到的方法会在其他的文档中做出详细的描述。

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; 即可。

这里一个一个找也是有不同的方式的:

  1. 单纯的用 if 判别;
  2. 按位”异或“操作;
  3. 算出 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。


思路:

这里给出两个思路:

  1. 滑动窗口:挨个找每个元素的滑动窗口。
  2. 动态规划:之后专门文档讲解动态规划的种种。有关本题目的动态规划相对容易,不需要显示 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;
    }
};

合并两个有序数组

描述:

给你两个有序整数数组 nums1nums2,请你将 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] + " ");
        }
    }
}

// 思路 2
class 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;
    }
};
// 思路 3
class 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;
    }
};
正文完
 0