带你刷算法——数组/字符串的完全掌握(一)(上)

简介: 带你刷算法——数组/字符串的完全掌握(一)


1.合并两个有序数组


给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2

中的元素数目。


请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。


注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m

个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。


示例 :

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3

输出:[1,2,2,3,5,6] 解释:需要合并 [1,2,3] 和 [2,5,6] 。 合并结果是 [1,2,2,3,5,6]

,其中斜体加粗标注的为 nums1 中的元素。 示例 2:


输入:nums1 = [1], m = 1, nums2 = [], n = 0 输出:[1] 解释:需要合并 [1] 和 [] 。

合并结果是 [1] 。 示例 3:


输入:nums1 = [0], m = 0, nums2 = [1], n = 1 输出:[1] 解释:需要合并的数组是 [] 和 [1]

。 合并结果是 [1] 。 注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0

仅仅是为了确保合并结果可以顺利存放到 nums1 中。


提示:

nums1.length == m + n nums2.length == n 0 <= m,

n <= 200 1 <= m + n <=200 -109 <= nums1[i],

nums2[j] <= 109



题解


思路一:暴力


import java.util.Arrays;
public class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        // 将 nums2 合并到 nums1 的后面
        for (int i = 0; i < n; ++i) {
            nums1[m + i] = nums2[i];
        }
        // 对合并后的数组进行排序
        Arrays.sort(nums1);
    }
}


这段代码的主要思路是将数组nums2合并到数组nums1的后面,然后对合并后的数组进行排序。


首先,通过一个循环将nums2中的元素逐个复制到nums1的末尾。循环变量i从0开始,每次迭代将nums2[i]赋值给nums1[m + i],其中m是nums1中已有元素的数量。这样就完成了合并的过程。


接下来,使用Arrays.sort()方法对合并后的数组nums1进行升序排序。这个方法会自动对数组中的元素进行排序,使得最终的数组呈现升序排列。


需要注意的是,这种实现方式会修改原始的nums1数组。如果不想修改原始数组,可以先创建一个临时数组,并将nums1复制到临时数组中,然后在临时数组上进行合并和排序操作。


思路二:双指针


public class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m - 1;
        int j = n - 1;
        int k = m + n - 1;
        while (i >= 0 && j >= 0) {
            if (nums1[i] > nums2[j]) {
                nums1[k--] = nums1[i--];
            } else {
                nums1[k--] = nums2[j--];
            }
        }
        while (j >= 0) {
            nums1[k--] = nums2[j--];
        }
    }
}


这段代码实现的思路是将两个有序数组nums1和nums2合并为一个有序数组,并将结果存储在nums1中。


首先,定义三个指针i、j和k。i指向nums1中的最后一个元素(有效元素),j指向nums2中的最后一个元素,k指向合并后的结果应该存储的位置。


然后,通过一个循环遍历两个数组,从后往前比较nums1[i]和nums2[j]的大小。如果nums1[i]大于nums2[j],就将nums1[i]放入合并后的结果数组的末尾(即nums1[k]),并将i和k分别减一。如果nums1[i]小于等于nums2[j],就将nums2[j]放入合并后的结果数组的末尾,并将j和k分别减一。重复这个过程直到其中一个数组遍历完。


最后,如果nums2中还有剩余元素(即j >= 0),则将剩余元素依次放入合并后的结果数组的末尾。


这种方式不需要创建额外的数组,只需要在原始的nums1数组上进行操作,节省了空间复杂度。同时,由于两个数组都是有序的,所以可以通过从后往前比较的方式,避免了频繁移动元素和插入操作,提高了效率。


2.移除元素



给你一个数组 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


/


题解


思路一:暴力法


public class Solution {
    public int removeElement(int[] nums, int val) {
        int i = 0;
        int n = nums.length;
        while (i < n) { // 当前指针位置小于数组长度时进行遍历
            if (nums[i] == val) { // 如果当前元素等于 val,则将最后一个元素复制到当前位置
                nums[i] = nums[n - 1];
                n--; // 数组长度减少1
            } else {
                i++; // 否则,移动指针到下一个位置
            }
        }
        return n; // 返回新的数组长度
    }
}


这段代码的主要思路是移除数组nums中所有值为val的元素,并返回新的数组长度。


首先,定义两个指针i和n。i表示当前遍历到的位置,n表示当前有效元素的数量,即数组的长度。


然后,通过一个循环遍历数组nums。在每次迭代中,首先检查当前位置nums[i]是否等于目标值val。如果相等,则将最后一个元素nums[n - 1]复制到当前位置nums[i]上,同时将数组长度n减1,表示有效元素的数量减少了1。这样就实现了将目标值移到数组末尾的操作。


如果当前位置nums[i]不等于目标值val,则直接将指针i向后移动一位,继续遍历下一个元素。


重复这个过程直到遍历完整个数组。最终,数组中所有值为val的元素都被移动到了末尾,并且数组的长度变为n。


最后,返回新的数组长度n作为结果。


这种方法通过双指针操作,避免了频繁的元素搬移,提高了效率。由于题目并未要求删除特定的元素,只需要将目标值移到数组末尾,并返回新的数组长度,所以元素的相对顺序并不重要。


优化

class Solution {
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length;
        while (left < right) {
            if (nums[left] == val) {
                nums[left] = nums[right - 1];
                right--;
            } else {
                left++;
            }
        }
        return left;
    }
}


这段代码的主要思路与之前给出的代码类似,都是移除数组nums中所有值为val的元素,并返回新的数组长度。


首先,定义两个指针left和right。left表示当前遍历到的位置,right表示有效元素的右边界,初始时为数组的长度。


然后,通过一个循环遍历数组nums。在每次迭代中,首先检查当前位置nums[left]是否等于目标值val。如果相等,则将最后一个元素nums[right - 1]复制到当前位置nums[left]上,同时将有效元素的右边界right减1,表示有效元素的数量减少了1。这样就实现了将目标值移到数组末尾的操作。


如果当前位置nums[left]不等于目标值val,则直接将左指针left向后移动一位,继续遍历下一个元素。


重复这个过程直到左指针left与右指针right相遇,即遍历完整个数组。最终,数组中所有值为val的元素都被移动到了末尾,并且左指针left所指示的位置即为新的数组长度。


最后,返回左指针left作为结果。


这种方法同样通过双指针操作,避免了频繁的元素搬移,提高了效率。与前一个代码不同的是,这里使用了左指针和右指针,在处理元素的替换时更加简洁。


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



给你一个 升序排列 的数组 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

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


提示:


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



题解


思路一:暴力法


class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        int k = 1;  // 记录唯一元素的数量
        for (int i = 1; i < n; i++) {
            if (nums[i] != nums[i-1]) {  // 当前元素与前一个元素不相等
                nums[k] = nums[i];  // 将当前元素移动到正确位置
                k++;
            }
        }
        return k;
    }
}


这段代码使用一个指针 k 来记录唯一元素的数量。初始化 k 为 1,表示第一个元素肯定是唯一的。


然后从数组的第二个元素开始遍历,如果当前元素与前一个元素不相等,则将当前元素移动到正确的位置,即数组的第 k 个位置,并将 k 自增 1。


最后返回 k,即为删除重复元素后的新数组长度。


请注意,这段代码要求输入的数组 nums 是升序排列的,才能正确删除重复元素。


思路二:快慢指针


class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        int fast = 1, slow = 1;
        while (fast < n) {
            if (nums[fast] != nums[fast - 1]) {
                nums[slow] = nums[fast];
                ++slow;
            }
            ++fast;
        }
        return slow;
    }
}


这段代码的主要思路是使用双指针方法来移除已排序数组中的重复元素,并返回修改后的数组长度。

具体步骤如下:


  1. 首先,获取数组的长度n。
  2. 如果数组为空(即n为0),则直接返回0,因为没有重复元素需要移除。
  3. 初始化两个指针:fast和slow,初始值都为1。其中,fast指针用于遍历数组,从第二个元素开始;slow指针用于记录最后一个非重复元素的位置。
  4. 进入循环,遍历数组直到fast指针达到数组末尾。
  5. 在每次迭代中,比较当前fast指针位置的元素与其前一个元素是否相等。如果不相等,说明找到了一个新的不重复元素。
  6. 将该新的不重复元素赋值给slow指针所在的位置,覆盖掉原有的重复元素。
  7. slow指针向后移动一位,指向下一个可能的不重复元素的位置。
  8. fast指针也向后移动一位,继续遍历数组。
  9. 循环结束后,返回slow指针的值,它表示修改后的数组的长度。由于slow指针记录的是最后一个非重复元素的位置,所以长度为slow表示没有重复元素的子数组的长度。
  10. 总结起来,这段代码通过快慢指针的方式,在遍历数组过程中,将不重复的元素移动到数组的前面,并且记录下不重复子数组的长度。最终实现了原地修改数组,去除重复元素的目的。
相关文章
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
3月前
|
算法 测试技术
【算法】二分算法——寻找旋转排序数组中的最小值
【算法】二分算法——寻找旋转排序数组中的最小值
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
28 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
1月前
|
算法
两个字符串匹配出最长公共子序列算法
本文介绍了最长公共子序列(LCS)问题的算法实现,通过动态规划方法求解两个字符串的最长公共子序列,并提供了具体的编程实现细节和示例。
71 1
两个字符串匹配出最长公共子序列算法
|
1月前
|
存储 算法 定位技术
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
这篇文章主要介绍了稀疏数组和队列的概念、应用实例以及如何使用数组模拟队列和环形队列的实现方法。
20 0
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
|
3月前
|
算法 Java
掌握算法学习之字符串经典用法
文章总结了字符串在算法领域的经典用法,特别是通过双指针法来实现字符串的反转操作,并提供了LeetCode上相关题目的Java代码实现,强调了掌握这些技巧对于提升算法思维的重要性。
|
3月前
|
存储 算法 Java
深入算法基础二分查找数组
文章深入学习了二分查找算法的基础,通过实战例子详细解释了算法的逻辑流程,强调了确定合法搜索边界的重要性,并提供了Java语言的代码实现。
深入算法基础二分查找数组
|
3月前
|
算法
【Azure Developer】完成算法第4版书中,第一节基础编码中的数组函数 histogrm()
【Azure Developer】完成算法第4版书中,第一节基础编码中的数组函数 histogrm()
|
3月前
|
算法
【算法】模拟算法——外观数组(medium)
【算法】模拟算法——外观数组(medium)
|
3月前
|
算法
【算法】前缀和——除自身以外数组的乘积
【算法】前缀和——除自身以外数组的乘积