ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

刷题笔记3.数组、链表、跳表

2022-04-27 16:35:17  阅读:272  来源: 互联网

标签:head ListNode int next 链表 跳表 height 刷题


数组、链表、跳表

链表

JAVA:LinkedList 双向链表

image-20211029093449171

image-20211029093751285

缺陷:lookup

image-20211029093816414

数组时间复杂度:

image-20211029093852169

对链表查找进行优化:跳表(理解原理redis)

空间换时间、添加一级索引

image-20211029094204044

image-20211029094246916

增加多级索引 log2n级

image-20211029094603697

image-20211029094657159

数组实战题目

283. 移动零

双指针
class Solution {
    public void moveZeroes(int[] nums) {
        int n=nums.length;
        int j=0;
        for(int i=0;i<n;i++){
            if(nums[i]!=0){
                nums[j]=nums[i];
                if(i!=j){
                    nums[i]=0;
                }
            j++;
            }
        }
    }
}
  • 遍历数组,无为0的元素移动数组前方,用index下标记录。
  • 遍历结束,对index值后的元素统一设为0
class Solution {
    public void moveZeroes(int[] nums) {
      int index = 0;
      for(int num:nums){
          if(num!=0){
              nums[index++]=num;
          }
      }
      while(index<nums.length){
          nums[index++] = 0;
      }
    }
}

42. 接雨水

难度困难2847收藏分享切换为英文接收动态反馈

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

img

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 
1.按列求雨水

找到此列 左右两边最大最高的列

  • 较矮的墙的高度大于当前列的墙的高度

image.png

把正在求的列左边最高的墙和右边最高的墙确定后,然后为了方便理解,我们把无关的墙去掉。

image.png

遍历每一列,然后分别求出这一列两边最高的墙。找出较矮的一端,和当前列的高度比较,结果就是上边的三种情况。

public int trap(int[] height) {
    int sum = 0;
    //最两端的列不用考虑,因为一定不会有水。所以下标从 1 到 length - 2
    for (int i = 1; i < height.length - 1; i++) {
        int max_left = 0;
        //找出左边最高
        for (int j = i - 1; j >= 0; j--) {
            if (height[j] > max_left) {
                max_left = height[j];
            }
        }
        int max_right = 0;
        //找出右边最高
        for (int j = i + 1; j < height.length; j++) {
            if (height[j] > max_right) {
                max_right = height[j];
            }
        }
        //找出两端较小的
        int min = Math.min(max_left, max_right);
        //只有较小的一段大于当前列的高度才会有水,其他情况不会有水
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}
2.动态规划

我们注意到,解法二中。对于每一列,我们求它左边最高的墙和右边最高的墙,都是重新遍历一遍所有高度,这里我们可以优化一下。

首先用两个数组,max_left [i] 代表第 i 列左边最高的墙的高度,max_right[i] 代表第 i 列右边最高的墙的高度。(一定要注意下,第 i 列左(右)边最高的墙,是不包括自身的,和 leetcode 上边的讲的有些不同)

max_left [i] = Max(max_left [i-1],height[i-1])。它前边的墙的左边的最高高度和它前边的墙的高度选一个较大的,就是当前列左边最高的墙了。

max_right[i] = Max(max_right[i+1],height[i+1]) 。它后边的墙的右边的最高高度和它后边的墙的高度选一个较大的,就是当前列右边最高的墙了。

3.双指针

例如这道题中,可以看到,max_left [ i ] 和 max_right [ i ] 数组中的元素我们其实只用一次,然后就再也不会用到了。所以我们可以不用数组,只用一个元素就行了。我们先改造下 max_left。

public int trap(int[] height) {
    int sum = 0;
    int max_left = 0;
    int[] max_right = new int[height.length];
    for (int i = height.length - 2; i >= 0; i--) {
        max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
    }
    for (int i = 1; i < height.length - 1; i++) {
        max_left = Math.max(max_left, height[i - 1]);
        int min = Math.min(max_left, max_right[i]);
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}

我们成功将 max_left 数组去掉了。但是会发现我们不能同时把 max_right 的数组去掉,因为最后的 for 循环是从左到右遍历的,而 max_right 的更新是从右向左的。

所以这里要用到两个指针,left 和 right,从两个方向去遍历。

那么什么时候从左到右,什么时候从右到左呢?根据下边的代码的更新规则,我们可以知道

max_left = Math.max(max_left, height[i - 1]);

height [ left - 1] 是可能成为 max_left 的变量, 同理,height [ right + 1 ] 是可能成为 right_max 的变量。

只要保证 height [ left - 1 ] < height [ right + 1 ] ,那么 max_left 就一定小于 max_right

因为 max_left 是由 height [ left - 1] 更新过来的,而 height [ left - 1 ] 是小于 height [ right + 1] 的,而 height [ right + 1 ] 会更新 max_right,所以间接的得出 max_left 一定小于 max_right。

反之,我们就从右到左更。

public int trap(int[] height) {
    int sum = 0;
    int max_left = 0;
    int max_right = 0;
    int left = 1;
    int right = height.length - 2; // 加右指针进去
    for (int i = 1; i < height.length - 1; i++) {
        //从左到右更
        if (height[left - 1] < height[right + 1]) {
            max_left = Math.max(max_left, height[left - 1]);
            int min = max_left;
            if (min > height[left]) {
                sum = sum + (min - height[left]);
            }
            left++;
        //从右到左更
        } else {
            max_right = Math.max(max_right, height[right + 1]);
            int min = max_right;
            if (min > height[right]) {
                sum = sum + (min - height[right]);
            }
            right--;
        }
    }
    return sum;
}
class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int left = 0, right = height.length - 1;
        int leftMax = 0, rightMax = 0;
        while (left < right) {
            leftMax = Math.max(leftMax, height[left]);
            rightMax = Math.max(rightMax, height[right]);
            if (height[left] < height[right]) {
                ans += leftMax - height[left];
                ++left;
            } else {
                ans += rightMax - height[right];
                --right;
            }
        }
        return ans;
    }
}
4 单调栈

image.png

说到栈,我们肯定会想到括号匹配了。我们仔细观察蓝色的部分,可以和括号匹配类比下。每次匹配出一对括号(找到对应的一堵墙),就计算这两堵墙中的水。

总体的原则就是,

  • 当前高度小于等于栈顶高度,入栈,指针后移。

  • 当前高度大于栈顶高度,出栈,计算出当前墙和栈顶的墙之间水的多少,然后计算当前的高度和新栈的高度的关系,重复第 2 步。直到当前墙的高度不大于栈顶高度或者栈空,然后把当前墙入栈,指针后移。

我们看具体的例子。

首先将 height [ 0 ] 入栈。然后 current 指向的高度大于栈顶高度,所以把栈顶 height [ 0 ] 出栈,然后栈空了,再把 height [ 1 ] 入栈。current 后移。

image.png

  • 然后 current 指向的高度小于栈顶高度,height [ 2 ] 入栈,current 后移。

image.png

然后 current 指向的高度大于栈顶高度,栈顶 height [ 2 ] 出栈。计算 height [ 3 ] 和新的栈顶之间的水。计算完之后继续判断 current 和新的栈顶的关系。

image.png

  • current 指向的高度大于栈顶高度,栈顶 height [ 1 ] 出栈,栈空。所以把 height [ 3 ] 入栈。currtent 后移。

image.png

  • 然后 current 指向的高度小于栈顶 height [ 3 ] 的高度,height [ 4 ] 入栈。current 后移。

image.png

  • 然后 current 指向的高度小于栈顶 height [ 4 ] 的高度,height [ 5 ] 入栈。current 后移。

然后 current 指向的高度大于栈顶 height [ 5 ] 的高度,将栈顶 height [ 5 ] 出栈,然后计算 current 指向的墙和新栈顶 height [ 4 ] 之间的水。计算完之后继续判断 current 的指向和新栈顶的关系。此时 height [ 6 ] 不大于栈顶 height [ 4 ] ,所以将 height [ 6 ] 入栈。current 后移。

image-20211110122410014 image-20211110122442367 image-20211110122401566
public int trap6(int[] height) {
    int sum = 0;
    Stack<Integer> stack = new Stack<>();
    int current = 0;
    while (current < height.length) {
        //如果栈不空并且当前指向的高度大于栈顶高度就一直循环
        while (!stack.empty() && height[current] > height[stack.peek()]) {
            int h = height[stack.peek()]; //取出要出栈的元素
            stack.pop(); //出栈
            if (stack.empty()) { // 栈空就出去
                break; 
            }
            int distance = current - stack.peek() - 1; //两堵墙之前的距离。
            int min = Math.min(height[stack.peek()], height[current]);
            sum = sum + distance * (min - h);
        }
        stack.push(current); //当前指向的墙入栈
        current++; //指针后移
    }
    return sum;
}

11. 盛最多水的容器

image-20211029111511150

双指针

image-20211029112017598

在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽 底边宽度 -1 变短:

若向内 移动短板 ,水槽的短板 min(h[i], h[j]) 可能变大,因此下个水槽的面积 可能增大 。
若向内 移动长板 ,水槽的短板 min(h[i], h[j]) 不变或变小,因此下个水槽的面积 一定变小

因此,初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积。

public class Solution {
    public int maxArea(int[] height) {
        int l = 0, r = height.length - 1;
        int ans = 0;
        while (l < r) {
            int area = Math.min(height[l], height[r]) * (r - l);
            ans = Math.max(ans, area);
            if (height[l] <= height[r]) {
                ++l;
            }
            else {
                --r;
            }
        }
        return ans;
    }
}


70. 爬楼梯

dp[i]=dp[i-1]+dp[i-2];

class Solution {
    public int climbStairs(int n) {
        int[] dp=new int[n+1];
        dp[0]=1;
        dp[1]=1;
        if(n<=1){
            return dp[n];
        }
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

排序+双指针
  • 首先对数组进行排序,排序后固定一个数 nums[i],再使用左右指针指向 nums[i]后面的两端,数字分别为 nums[L] 和 nums[R],计算三个数的和 sumsum 判断是否满足为 00,满足则添加进结果集
  • 如果 nums[i]大于 0,则三数之和必然无法等于 0,结束循环
  • 如果 nums[i] == nums[i-1],则说明该数字重复,会导致结果重复,所以应该跳过
  • 当 sum == 0 时,nums[L]== nums[L+1 则会导致结果重复,应该跳过,L++
  • 当 sum== 0 时,nums[R]== nums[R-1]则会导致结果重复,应该跳过,R--
  • 时间复杂度:O(n^2)O,nn 为数组长度

image-20211029165758260image-20211029170119050

class Solution {
    public static List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ans = new ArrayList();
        int len = nums.length;
        if(nums == null || len < 3) return ans;
        Arrays.sort(nums); // 排序
        for (int i = 0; i < len ; i++) {
            if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
            if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
            int L = i+1;
            int R = len-1;
            while(L < R){
                int sum = nums[i] + nums[L] + nums[R];
                if(sum == 0){
                    ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
                    while (L<R && nums[L] == nums[L+1]) L++; // 去重
                    while (L<R && nums[R] == nums[R-1]) R--; // 去重
                    L++;
                    R--;
                }
                else if (sum < 0) L++;
                else if (sum > 0) R--;
            }
        }        
        return ans;
    }
}

up-16. 最接近的三数之和

难度中等996收藏分享切换为英文接收动态反馈

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

示例 1:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
class Solution {
    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int best=1000000//不能用MAXVLUE,减去target可能会溢出
        for(int i=0;i<nums.length;i++){
            if(i>0&&nums[i]==nums[i-1]) continue;
            int left=i+1;
            int right=nums.length-1;
            while(left<right){
                int sum=nums[left]+nums[right]+nums[i];
                if(sum==target) return sum;
                if (Math.abs(sum - target) < Math.abs(best - target)) {
                        best = sum;
                    }

                if(sum>target){
                    //右边找到下一个不相等的数
                    right--;
                    while(left<right&&nums[right]==nums[right+1]){
                        right--;
                    }          
                }else{
                    //左边找到下一个不相等的数
                    left++;
                    while(left<right&&nums[left]==nums[left-1]){
                        left++;
                    }  
                }
            }
        }
        return best;
    }
}

54. 螺旋矩阵

难度中等955收藏分享切换为英文接收动态反馈

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 1:

img

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        int m=matrix.length,n=matrix[0].length;
        int right=n-1,bottom=m-1,left=0,top=0;
        int x=0,y=0;
        List<Integer> res=new ArrayList<>();
        while(true){
            for(int i=left;i<=right;i++){
                res.add(matrix[top][i]);
            }
            if(++top>bottom) break;
            for(int i=top;i<=bottom;i++){
                res.add(matrix[i][right]);
            }
            if(--right<left) break;
            for(int i=right;i>=left;i--){
                res.add(matrix[bottom][i]);
            }
            if(--bottom<top) break;
            
            for(int i=bottom;i>=top;i--){
                res.add(matrix[i][left]);
            }
            if(++left>right) break;
        }
        return res;
    }
}

链表实战题目

206. 反转链表

1.双指针
class Solution {
    public ListNode reverseList(ListNode head) {
    if(head==null) return null;
    ListNode node1= head;
    ListNode node2= head.next;
    node1.next=null;
    while(node2!=null){
        ListNode temp =node2.next;
        node2.next=node1;
        node1=node2;
        node2=temp;
    }	
    return node1;
    }
}
2.简洁的递归

使用递归函数,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作 retret .
此后,每次函数在返回的过程中,让当前结点的下一个结点的 next 指针指向当前节点。
同时让当前结点的 next指针指向 NULL ,从而实现从链表尾部开始的局部反转
当递归函数全部出栈后,链表反转完成。

动画演示如下:
递归.gif

反转链表-递归过程.jpg
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode p = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return p;
    }
}

92. 反转链表 II

难度中等1115收藏分享切换为英文接收动态反馈

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

示例 1:

img

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
头插法

img1.png

class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        // 定义一个dummyHead, 方便处理
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;

        // 初始化指针
        ListNode g = dummyHead;
        ListNode p = dummyHead.next;

        // 将指针移到相应的位置
        for(int step = 0; step < m - 1; step++) {
            g = g.next; p = p.next;
        }

        // 头插法插入节点
        for (int i = 0; i < n - m; i++) {
            ListNode removed = p.next;
            p.next = p.next.next;

            removed.next = g.next;
            g.next = removed;
        }

        return dummyHead.next;
    }
}

24. 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换

image-20211030101725252

1.不加虚拟头节点
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head==null) return null;
        if(head.next==null) return head;
        ListNode cur=head.next;
        ListNode pre=head;
        ListNode temp =cur.next;
        pre.next=cur.next;
        cur.next=pre;
        head = cur; 
        while(temp!=null&&temp.next!=null){    
            cur=temp.next;
            pre.next=cur;
            pre=temp;
            temp =cur.next;
            pre.next=cur.next;
            cur.next=pre;  
        }
        return head;
    }
}
2.加上虚拟头节点

image-20211030102901129

image-20211030101456938

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode temp = dummyHead;
        while (temp.next != null && temp.next.next != null) {
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;
            temp.next = node2;
            node1.next = node2.next;
            node2.next = node1;
            temp = node1;
        }
        return dummyHead.next;
    }
}
3递归
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head==null||head.next==null) return head;
        ListNode newHead=head.next;
        head.next=swapPairs(newHead.next);
        newHead.next=head;
        return newHead;
    }
}

141. 环形链表

1.哈希表

思路及算法

最容易想到的方法是遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。

具体地,我们可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可

public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<ListNode>();
        while (head != null) {
            if (!seen.add(head)) {
                return true;
            }
            head = head.next;
        }
        return false;
    }
}
快慢指针

slow,fast

假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈

image-20211030110906346

image-20211030110927220

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while (slow != fast) {
            if (fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}

142. 环形链表 II

方法一:哈希表

思路与算法

一个非常直观的思路是:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<ListNode>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}
方法二:双指针

这类链表题目一般都是使用双指针法解决的,例如寻找距离尾部第K个节点、寻找环入口、寻找公共尾部入口等

设链表共有 a+b个节点

双指针第一次相遇: 设两指针 fastslow 指向链表头部 headfast 每轮走 2 步,slow 每轮走 1 步

第一种结果: fast 指针走过链表末端,说明链表无环,直接返回 null;

TIPS: 若有环,两指针一定会相遇。因为每走 1 轮,fast 与 slow 的间距 +1,fast 终会追上 slow

第二种结果:fast == slow时, 两指针在环中 第一次相遇 。下面分析此时fastslow走过的 步数关系

  1. fast 走的步数是slow步数的 22 倍,即 f = 2sf=2s;(解析: fast 每轮走 22 步)
  2. fastslow多走了 nn 个环的长度,即 f = s + nb;
  3. 以上两式相减得:f = 2nb,s = nb,即fastslow 指针分别走了 2n,n 个 环的周长

如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=a+nb,此时只需要让slow再走a步就能到达入口,所以一个新的指针从head开始能和slow同时再走a步到达入口

image-20211030114926024

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null) return null;
        ListNode slow=head;
        ListNode fast=head;
        while(true){
            if(fast==null||fast.next==null) return null;
            slow=slow.next;
            fast=fast.next.next;
            if(fast==slow) break;
        }
        ListNode temp=head;
        while(slow!=temp){
            slow=slow.next;
            temp=temp.next;
        }
        return slow;
    }
}

25. K 个一组翻转链表

难度困难

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

image-20211030215629709

步骤分解:

  1. 链表分区为已翻转部分+待翻转部分+未翻转部分
  2. 每次翻转前,要确定翻转链表的范围,这个必须通过 k 此循环来确定
  3. 需记录翻转链表前驱和后继,方便翻转完成后把已翻转部分和未翻转部分连接起来
  4. 初始需要两个变量 pre 和 end,pre 代表待翻转链表的前驱,end 代表待翻转链表的末尾
  5. 经过k此循环,end 到达末尾,记录待翻转链表的后继 next = end.next
  6. 翻转链表,然后将三部分链表连接起来,然后重置 pre 和 end 指针,然后进入下一次循环
  7. 特殊情况,当翻转部分长度不足 k 时,在定位 end 完成后,end==null,已经到达末尾,说明题目已完成,直接返回即可
  8. 时间复杂度为 O(n*K)O(n∗K) 最好的情况为 O(n)O(n) 最差的情况未 O(n^2)
  9. 空间复杂度为 O(1)O(1) 除了几个必须的节点指针外,我们并没有占用其他空间

image-20211030215533093

image-20211030215549468

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null){
            return head;
        }
        //定义一个假的节点。
        ListNode dummy=new ListNode(0);
        //假节点的next指向head。
        // dummy->1->2->3->4->5
        dummy.next=head;
        //初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
        ListNode pre=dummy;
        ListNode end=dummy;

        while(end.next!=null){
            //循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
            //dummy->1->2->3->4->5 若k为2,循环2次,end指向2
            for(int i=0;i<k&&end != null;i++){
                end=end.next;
            }
            //如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
            if(end==null){
                break;
            }
            //先记录下end.next,方便后面链接链表
            ListNode next=end.next;
            //然后断开链表
            end.next=null;
            //记录下要翻转链表的头节点
            ListNode start=pre.next;
            //翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
            pre.next=reverse(start);
            //翻转后头节点变到最后。通过.next把断开的链表重新链接。
            start.next=next;
            //将pre换成下次要翻转的链表的头结点的上一个节点。即start
            pre=start;
            //翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
            end=start;
        }
        return dummy.next;


    }
    //链表翻转
    // 例子:   head: 1->2->3->4
    public ListNode reverse(ListNode head) {
         //单链表为空或只有一个节点,直接返回原单链表
        if (head == null || head.next == null){
            return head;
        }
        //前一个节点指针
        ListNode preNode = null;
        //当前节点指针
        ListNode curNode = head;
        //下一个节点指针
        ListNode nextNode = null;
        while (curNode != null){
            nextNode = curNode.next;//nextNode 指向下一个节点,保存当前节点后面的链表。
            curNode.next=preNode;//将当前节点next域指向前一个节点   null<-1<-2<-3<-4
            preNode = curNode;//preNode 指针向后移动。preNode指向当前节点。
            curNode = nextNode;//curNode指针向后移动。下一个节点变成当前节点
        }
        return preNode;

    }


}

143. 重排链表

难度中等752收藏分享切换为英文接收动态反馈

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

img

输入:head = [1,2,3,4]
输出:[1,4,2,3]
快慢指针+反转链表+合并链表
public void reorderList(ListNode head) {
    if (head == null) {
        return;
    }

    // 获得中间节点
    ListNode mid = findMid(head);

    // 中间节点之后的部分进行反转
    ListNode head2 = mid.next;
    mid.next = null;
    head2 = reverseList(head2);

    // 合并
    ListNode head1 = head;
    mergeList(head1, head2);
}

// LeetCode 876
private ListNode findMid(ListNode head){
    ListNode slow = head;
    ListNode fast = head;
    while (fast.next != null && fast.next.next != null) {
        slow = slow.next;
        fast= fast.next.next;
    }
    return slow;
}

// LeetCode 206
private ListNode reverseList(ListNode head){
    ListNode prev = null;
    ListNode cur = head;
    while (cur != null) {
        ListNode nextNode = cur.next;
        cur.next = prev;
        prev =cur;
        cur = nextNode;
    }
    return prev;
}


private void mergeList(ListNode head1, ListNode head2) {
    ListNode next1 = null;
    ListNode next2 = null;
    while (head1 != null && head2 != null) {
        next1 = head1.next;
        next2 = head2.next;

        head1.next = head2;
        head1 = next1;

        head2.next = head1;
        head2 = next2;
    }
}

82. 删除排序链表中的重复元素 II

难度中等776收藏分享切换为英文接收动态反馈

存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。

返回同样按升序排列的结果链表。

示例 1:

img

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
1).递归实现
    public ListNode deleteDuplicates(ListNode head) {
        // 没有节点或者只有一个节点,必然没有重复元素
        if (head == null || head.next == null) return head;

        // 当前节点和下一个节点,值不同,则head的值是需要保留的,对head.next继续递归
        if (head.val != head.next.val) {
            head.next = deleteDuplicates(head.next);
            return head;
        } else {
            // 当前节点与下一个节点的值重复了,重复的值都不能要。
            // 一直往下找,找到不重复的节点。返回对不重复节点的递归结果
            ListNode notDup = head.next.next;
            while (notDup != null && notDup.val == head.val) {
                notDup = notDup.next;
            }
            return deleteDuplicates(notDup);
        }
    }
2).利用有序性质一次排序
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // Set<ListNode> set=new HashSet<>();
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        ListNode pre=dummy;
        ListNode cur=head;
        while(cur!=null&&cur.next!=null){
            if(cur.val==cur.next.val){
                while(cur!=null&&cur.next!=null &&cur.val==cur.next.val){
                    cur=cur.next;
                }
                cur=cur.next;
                pre.next=cur;
            }else{
                pre=pre.next;
                cur=cur.next;
            }
            
        }
        return dummy.next;
    }
}

138. 复制带随机指针的链表

难度中等823

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点

例如,如果原链表中有 XY 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 xy ,同样有 x.random --> y

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0n-1);如果不指向任何节点,则为 null

你的代码 接受原链表的头节点 head 作为传入参数。

示例 1:

img

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
迭代

第一步,根据遍历到的原节点创建对应的新节点,每个新创建的节点是在原节点后面,比如下图中原节点1不再指向原原节点2,而是指向新节点1

5.jpg

第二步是最关键的一步,用来设置新链表的随机指针
6.jpg

第三步就简单了,只要将两个链表分离开,再返回新链表就可以了

class Solution {
    public Node copyRandomList(Node head) {
        if(head==null) {
            return null;
        }
        Node p = head;
        //第一步,在每个原节点后面创建一个新节点
        //1->1'->2->2'->3->3'
        while(p!=null) {
            Node newNode = new Node(p.val);
            newNode.next = p.next;
            p.next = newNode;
            p = newNode.next;
        }
        p = head;
        //第二步,设置新节点的随机节点
        while(p!=null) {
            if(p.random!=null) {
                p.next.random = p.random.next;
            }
            p = p.next.next;
        }
        Node dummy = new Node(-1);
        p = head;
        Node cur = dummy;
        //第三步,将两个链表分离
        while(p!=null) {
            cur.next = p.next;
            cur = cur.next;
            p.next = cur.next;
            p = p.next;
        }
        return dummy.next;
    }
}	
递归
  1. 哈希表Mydic映射原有节点->新的节点
  2. 原节点为空,则返回空
  3. 原节点在哈希表中可以找到,则说明新的节点已生成,直接返回
  4. 根据原有节点的值,创建新的节点root = Node(node.val)
  5. 将原有节点和新节点的对应关系添加到哈希表中Mydic[node] = root
  6. 最后参照原节点的next和random关系,创建新的next和random节点给新节点root
  7. 递归整个过程
class Solution {
    private HashMap<Node, Node> MyMap = new HashMap<>();
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        if (MyMap.containsKey(head)) return MyMap.get(head);
        Node root = new Node(head.val);
        MyMap.put(head, root);
        root.next = copyRandomList(head.next);
        root.random = copyRandomList(head.random);
        return root;
    }
}

常见题

189. 旋转数组

1.方法一:使用额外的数组

class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        int[] newArr = new int[n];
        for (int i = 0; i < n; ++i) {
            newArr[(i + k) % n] = nums[i];
        }
        System.arraycopy(newArr, 0, nums, 0, n);
    }
}
class Solution {
    public void rotate(int[] nums, int k) {
        if(nums.length<=k) {
            k=k%nums.length;
        } 
        k=nums.length-k;
        int[] temp=new int[k];
        for(int i=0;i<k;i++){
            temp[i]=nums[i];
        }
        for(int i=k;i<nums.length;i++){
            nums[i-k]=nums[i];
        }
        for(int i=0;i<k;i++){
            nums[nums.length-k+i]=temp[i];
        }
    }
}

方法三:数组翻转

image-20211031145034061

21. 合并两个有序链表

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

88. 合并两个有序数组

1.合并之后排序

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        for(int i=0;i<n;i++){
            nums1[m+i]=nums2[i];
        }
        Arrays.sort(nums1);        
    }
}

2.双指针

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = 0, p2 = 0;
        int[] sorted = new int[m + n];
        int cur;
        while (p1 < m || p2 < n) {
            if (p1 == m) {
                cur = nums2[p2++];
            } else if (p2 == n) {
                cur = nums1[p1++];
            } else if (nums1[p1] < nums2[p2]) {
                cur = nums1[p1++];
            } else {
                cur = nums2[p2++];
            }
            sorted[p1 + p2 - 1] = cur;
        }
        for (int i = 0; i != m + n; ++i) {
            nums1[i] = sorted[i];
        }
    }
}
3.逆序双指针

img

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = m-1, p2 = n-1;
        int tail=nums1.length-1;
        while(p2>=0){
            if(p1<0||nums2[p2]>=nums1[p1]){
                nums1[tail--]=nums2[p2--];
            }else{
                nums1[tail--]=nums1[p1--];
            }
        }
    }
}

66. 加一

class Solution {
    public int[] plusOne(int[] digits) {
        int n = digits.length;
        for (int i = n - 1; i >= 0; --i) {
            if (digits[i] != 9) {
                ++digits[i];
                for (int j = i + 1; j < n; ++j) {
                    digits[j] = 0;
                }
                return digits;
            }
        }

        // digits 中所有的元素均为 9
        int[] ans = new int[n + 1];
        ans[0] = 1;
        return ans;
    }
}

标签:head,ListNode,int,next,链表,跳表,height,刷题
来源: https://www.cnblogs.com/zyd123123/p/16199462.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有