硬核空间技术博客

leetcode数组,链表相关基础题目整理

2020-04-15  本文已影响0人  憨憨二师兄

leetcode26.删除排序数组中的重复项

关键字:数组
题目链接

解题思路:双指针迭代

使用一前一后两个指针,遍历迭代数组,如果两个指针指向的数字相等,那么右指针向后移;如果两个指针指向的数字不等,那么就将右指针指向的数字赋值给左指针指向的下一个位置,赋值完毕后,右指针向后移动;直至遍历到数组的最后一个位置。可以参考 删除重复项-带优化思路 这篇题解。
代码如下:

class Solution {
    public int removeDuplicates(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        int left = 0;
        int right = 1;
        while(right < nums.length){
            if(nums[left] != nums[right]){
                nums[++left] = nums[right];
            }
            right++;
        }
        return left + 1;
    }
}

本思路的时间复杂度为O(n),额外空间复杂度为O(1)。
执行结果:


leetcode6.加一

关键字:数组
题目链接

题解:

对于本题共有三种情况:

  1. 只加1不进位
    对于数组[6,7]而言,加一之后返回[6,8];我们只需要对数组的最后一个数字进行判断,加一之再对10取模,如果不等于0,我们就可以将数组直接返回。
  2. 进位,但没有越位
    进位不越位是什么意思呢?举个栗子:对于数组[7,9,9],加一之后应返回[8,0,0]。还是从数组最后一位向第一位迭代,直到首位加一对10取模不等于0返回,这样和第一种情况实际上相同,只不过我们需要对数组的每一位都进行一次遍历
  3. 越位
    越位只有一种情况,就是数组中的每一个数字都是9,举例:[9,9,9]加一后返回[1,0,0,0]。我们只需要将原数组赋给一个新的数组,新数组的长度为原数组长度加一,然后再将第一位置1即可。

代码如下:

class Solution {
    public int[] plusOne(int[] digits) {
        for(int i = digits.length - 1;i>=0;i--){
            digits[i]++;
            digits[i] %= 10;
            if(digits[i] != 0){
                return digits;
            }
        }
        digits = new int[digits.length + 1];
        digits[0] = 1;
        return digits;
    }
}

这种思路,时间复杂度为O(n),出现越位的情况时,额外空间复杂度为O(n),不越位额外空间复杂度O(1).
执行代码如下:


leetcode88.合并两个有序数组

关键字:数组
题目链接

题解一:正向迭代

使用三个指针,依次迭代推移,建议自己写写画画把这种基本的思路写出来,代码如下:

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

这种写法的时间复杂最坏的情况为O(n * m),没有使用额外的空间,所以额外空间复杂度为O(1).
执行结果如下:


题解二:反向迭代

正向迭代会导致逻辑复杂,最好的办法是从后向前遍历数组,设置指针len1与len2分别指向nums1和nums2的尾部,从尾部的值开始比较遍历,设置指针len指向nums1的最末尾(合并后的数组长度为n+m,所以len = n+m-1),每次遍历比较大小值后填充。
代码如下:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int len1 = m - 1;
        int len2 = n - 1;
        int len = n + m - 1;
        while(len1 >= 0 && len2 >= 0){
            nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--] : nums2[len2--];
        }
        System.arraycopy(nums2,0,nums1,0,len2 + 1);
    }
}

反向迭代会将时间复杂度缩小至O(n+m)级别,并且没有使用额外的空间,额外空间复杂度为O(1)。
执行结果:


leetcode189.旋转数组

关键字:数组
题目链接

题解一:暴力求解

最原始的方法就是将原数组旋转k次

class Solution {
    public void rotate(int[] nums, int k) {
        int first = 0;
        int temp = 0;
        for(int i = 0; i < k;i++){
            first = nums[nums.length - 1];
            for(int j = 0; j < nums.length;j++){
                temp = nums[j];
                nums[j] = first;
                first = temp;
            }
        }
    }
}

执行结果:


题解二:空间换时间

顾名思义,开辟长度为len的额外的数组空间,将旋转后的数组拷贝至辅助数组,其中对应的index关系为help[(i + k)%len] = nums[i] ,填满辅助数组以后,再将辅助数组拷贝至原数组中即可。
代码如下:

class Solution {
    public void rotate(int[] nums, int k) {
        if(nums == null || nums.length == 0){
            return;
        }
        int[] help = new int[nums.length];
        for(int i = 0;i < nums.length;i++){
            help[(i + k) % nums.length] = nums[i];
        }
        for(int i = 0;i < nums.length;i++){
            nums[i] = help[i];
        }
    }
}

执行结果:


题解三:

第三种思路是用反转来替换旋转:
对于数组[1,2,3,4,5,6,7] ,k = 3;旋转之后的数组为:[5,6,7,1,2,3,4]
实际上,相当于:

  1. 将数组反转
    [7,6,5,4,3,2,1]
  2. 将前k个数反转
    [5,6,7,4,3,2,1]
  3. 将index from k to len - 1,也就是剩余的部分反转
    [5,6,7,1,2,3,4]

我们通过这样的思想很容易写出代码:

class Solution {
    public void rotate(int[] nums, int k) {
        if(nums == null || nums.length == 0){
            return;
        }
        int len = nums.length;
        k %= len;
        reverse(nums,0,len - 1);
        reverse(nums,0,k - 1);
        reverse(nums,k,len - 1);
    }

    private void reverse(int[] nums,int start,int end){
        while(start < end){
            swap(nums,start++,end--);
        }
    }

    private void swap(int[] nums,int i,int j){
        nums[i] = nums[i] ^ nums[j];
        nums[j] = nums[i] ^ nums[j];
        nums[i] = nums[i] ^ nums[j];
    }
}

这种思路的时间复杂度为:O(n),额外空间复杂度为O(1)
执行结果:


leetcode21.合并两个有序链表

关键字:链表,递归
题目链接

题解一:Merge

本题其实和MergeSort中的Merge过程一模一样,只不过我们平时都是拿数组进行训练,本题将数组变成了链表而已,还是使用dummyHead作为头节点,返回dummyHead.next即可,代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {

        ListNode dummyHead = new ListNode(-1);
        ListNode cur = dummyHead;
        while (l1 != null && l2 != null) {
            if(l1.val <= l2.val){
                cur.next = new ListNode(l1.val);
                cur = cur.next;
                l1 = l1.next;
            }else{
                cur.next = new ListNode(l2.val);
                cur = cur.next;
                l2 = l2.next;
            }
        }
        while(l1 != null){
            cur.next = new ListNode(l1.val);
            cur = cur.next;
            l1 = l1.next;
        }
        while(l2 != null){
            cur.next = new ListNode(l2.val);
            cur = cur.next;
            l2 = l2.next;
        }
        return dummyHead.next;
    }
}

本代码中,时间复杂度为两个链表的总长度O(n+m),另外并没有在原有的链表上进行操作,所以额外空间复杂度为O(n+m).
执行代码结果:


题解二:Recursion

本题比较巧妙的办法就是递归,看代码也非常好理解,无论是链表还是二叉树都离不开递归,代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null){
            return l2;
        }
        if(l2 == null){
            return l1;
        }
        
        if(l1.val < l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

递归操作和merge操作的时间复杂度和额外空间复杂度是一样的,因为从时间复杂度上来看,递归操作最多也需要从头遍历两个链表的长度,额外空间上需要n+m个栈帧,所以额外空间复杂度也为O(n+m).
执行结果:


题解三:对题解一进行优化

有没有办法不浪费额外的空间呢,实际上在merge的过程中,只要避免创建新的节点即可;代码也不是很难理解:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummyHead = new ListNode(-1);
        ListNode cur = dummyHead;
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                cur.next = l1;
                l1 = l1.next;
            }else{
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = l1 == null ? l2 : l1;
        return dummyHead.next;
    }
}

时间复杂度O(n+m),因为在原有的链表上操作,空间复杂度优化至O(1).
执行结果如下:


leetcode25.k个一组翻转链表

关键字:链表
题目链接

题解:

图片转自 王小二 在 leetcode上面的题解:图解k个一组翻转链表


正如题解中所说,一图胜千言
代码如下:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;

        ListNode pre = dummyHead;
        ListNode end = dummyHead;

        while(end.next != null){
            for(int i = 0;i < k && end != null;i++){
                end = end.next;
            }
            if(end == null){
                break;
            }
            ListNode start = pre.next;
            ListNode next = end.next;
            end.next = null;
            pre.next = reverse(start);
            start.next = next;

            pre = start;
            end = pre;
        }

        return dummyHead.next;
    }

    private ListNode reverse(ListNode head){
        ListNode pre = null;
        ListNode next = null;
        while(head != null){
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
}

执行结果:


关于数组与链表的其他基础题目以及题解,在这里给出我的文章链接:

leetcode283.移动零
leetcode11.盛最多水的容器
leetcode70.爬楼梯
leetcode1.两数之和
leetcode15.三数之和
leetcode206.反转链表
leetcode24.两两交换链表中的节点
leetcode141.环形链表
leetcode142.环形链表II

上一篇下一篇

猜你喜欢

热点阅读