Leetcode(c++实现)

Leetcode(c++实现)

Data structure

Array

1. 两数之和

date: 22.2.22

给定一个整数数组 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
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

AC code1

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int i, j;
        for(i = 0; i < nums.size()-1; i++){
            for(j = i + 1; j < nums.size(); j++){
                if (nums[i] + nums[j] == target){
                    return {i, j};  
                }
            }
        }
        return {}; /* no answer should return null */
    };
};
  • 暴力匹配就可以了,注意j的变化在i之后。但O(n)O(n^2),太慢了。
  • knowlegle:size()返回一个vector得长度

AC code2

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        /* array map element to index */
        map<int, int> array_element_index;
        /* answer has two index */
        vector<int> answer(2, 0);
        for (int i = 0; i < nums.size(); i++)
            array_element_index.insert(map<int, int>::value_type(nums[i], i));
        for (int i = 0; i< nums.size(); i++){
            /* use count() find the element and can't be itself */
            if(array_element_index.count(target - nums[i]) && (array_element_index[target - nums[i]] != i)){
                answer[0] = i;
                answer[1] = array_element_index[target - nums[i]];
                break;
            }
        }
        return answer;
    };
};
  • 使用hash表,将原来的nums反过来映射一遍,没错,我们恰好是将数组中的元素(element)和索引(index)反过来了,在array_element_indexkey是element,value是index

  • 使用count()能非常巧妙的找出匹配的element,但注意不能是本身

    • 例如nums = [3, 2, 4], target = 6,如果是本身的话3+3 = 6,显然不符合题意
  • knowlegle

    • vector<int> answer(2, 0);是创建一个有两个初始数值为0的vector
    • count()是计算某个key出现的次数,如果没找到就返回0

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

date: 22.2.24

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

判题标准:

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

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 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:

0 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按 升序 排列

AC code1

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if (nums.size() < 2) 
            return nums.size();
        int slow = 0;
        for (int fast = 1; fast < nums.size(); fast++)
            if (nums[slow] != nums[fast])
                nums[++slow] = nums[fast];
        return ++slow;
    };
};
  • 双指针解法,slow是慢指针,fast是快指针,发现不相同的替换就可以了

AC code2

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        return process(nums, 1);
    }

    int process(vector<int>& nums, int k){
        int idx = 0;
        for(auto x : nums){
            if(idx < k || nums[idx - k] != x){
                nums[idx++] = x; /* use idx then idx+1 */
            }
        }
        return idx;  
    }
};
  • 速度不快,但是通用解法
  • 两个核心思想(保留/要求逻辑
    • 第一次保留前k位元素
    • 之后遇到不同的新元素时,也保留k位新元素

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 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:

0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100

AC code1

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow = 0;
        for (int fast = 0; fast < nums.size(); ++fast){
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                ++slow;
            }
        }
    return slow; /* when instead, ++slow => show not +1*/
    }
};
  • 快慢指针,由于例子少,速度也很快
  • 缺点:后面的无效段会重复赋值

AC code2

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int left = 0, right = nums.size();
        while (left < right) {
            if (nums[left] == val) {
                nums[left] = nums[right - 1];
                right--;
            } else {
                left++;
            }
        }
        return left;
    }
};
  • 还是双指针,但是左右指针,能不跑无效段
  • nums[right - 1];是为了解决边界条件,该式子没有改变right,只是取rigth - 1的值
  • if语句给相等更好,因为相等的情况比较少

AC code3

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int j = nums.size() - 1;
        for (int i = 0; i <= j; i++) {
            if (nums[i] == val) {
                nums[i--] = nums[j--];
            }
        }
        return j + 1;
    }
};
  • 相比AC code2更加简洁

AC code4

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int idx = 0;
        for(auto x : nums)
            if(x != val)nums[idx++] = x;
        return idx;
    }
};
  • AC code1类似,但写法更接近保留/要求逻辑

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4
示例 4:

输入: nums = [1,3,5,6], target = 0
输出: 0
示例 5:

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

提示:

1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 为无重复元素升序排列数组
-104 <= target <= 104

AC code1

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int mid;
        if (target > nums[right])
            return nums.size();
        while (left < right) {
            mid = left + (right - left) / 2; /* only "(right + left) / 2" maybe can't beyond border*/
            if (nums[mid] < target)
                left = mid + 1; /* beyond border => "left >= right" */
            else
                right = mid;
        }
        return left;
    }
};
  • 设定边界条件是left >= right,当left == right时,返回的left就是和target相同元素的索引

  • left > right时,说明没找到元素left将是第一个大于target的元素

  • size()可能是索引(参考示例4),除了单独提出来,也可以直接设right = nums.size()(参见AC code2

  • 一开始做的时候立即想到了二分法,但是mid和left的设置没考虑到“死锁”的问题(没办法退出while)

  • 参考链接:写对二分查找不是套模板并往里面填空,需要仔细分析题意

AC code2

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size();
        int mid;
        while (left < right) {
            mid = left + (right - left) / 2; /* only "(right + left) / 2" can't beyond border*/
            if (nums[mid] < target)
                left = mid + 1; /* beyond border => "left >= right" */
            else
                right = mid;
        }
        return left;
    }
};
posted @ 2022-02-22 22:41  duile  阅读(191)  评论(0编辑  收藏  举报