【LeetCode】线性表操作与双指针练习

线性表操作练习

双指针练习:移动零 283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

双层循环实现

class Solution {
    public void moveZeroes(int[] nums) {
    // 判断是否是0 如果是 就和后面的数交换 直到是最后一个数 是0就冒泡
        for(int i = 0; i< nums.length; i++){
            for(int j = i+1;j<nums.length;j++){
                if(nums[i]==0){
                    int temp = nums[i];
                    nums[i]=nums[j];
                    nums[j]=temp;
                }
            }
        }
    }
}

定义一个指针,保存非零元素,其他的补0实现

class Solution {
    // 先把非0元素覆盖掉原数组的部分值,最后统一补0
    public void moveZeroes(int[] nums) {
        int j = 0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]!=0){
                nums[j]=nums[i];
                j++;
            }
        }
        // 最后一把补0
        for(;j<nums.length;j++){
            nums[j]=0;
        }
    }     
    
    // 循环过程中把非0元素和0交换
    public void moveZeroes(int[] nums) {
        int j = 0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]!=0){
                nums[j]=nums[i];
                //循环过程中不断的写0
                if(i!=j){
                    nums[i]=0;
                }
                j++;
            }
        }
    }
}

删除数组中重复元素 26. 删除排序数组中的重复项

class Solution {
    public int removeDuplicates(int[] nums) {
        // 定义j为上个排序好的不重复的值
        int j = 0;
        for(int i=1;i<nums.length;i++){
            if(nums[j]!=nums[i]){
                j++;
                nums[j]=nums[i];
            }
        }
        return ++j;
    }
}

双指针夹逼练习:盛水容器 11. 盛最多水的容器

class Solution {
    // 双层循环法,计算每两个立柱之间的盛水量,获取最大值
    public int maxArea(int[] height) {
        int maxArea=0;
        for(int i = 0;i<height.length-1;i++){
            for(int j = i+1; j<height.length;j++){
                int area = (j-i)* Math.min(height[i],height[j]);
                maxArea = Math.max(maxArea,area);
            }
        }
        return maxArea;
    }
	
    // 双指针法:结合题目特点,先保持宽最大,然后不断的缩小宽更新高度,计算盛水量获取最大值
    // 移动指针的方式:每次将矮的柱子移动到下一个位置
    public int maxArea(int[] height) {
        int maxArea=0;

        int i = 0;
        int j = height.length -1;

        while(i!=j){
            int area = (j-i)*Math.min(height[i],height[j]);
            maxArea=Math.max(maxArea,area);
            if(height[i]>height[j]){
                j--;
            }else{
                i++;
            }
        }

        return maxArea;
    }

}

递归的循环实现:爬楼器 70. 爬楼梯

class Solution {
    // 递归实现 n只能到45左右 栈溢出
    public int climbStairs(int n) {
        if(n==1) return 1;
        if(n==2) return 2;
        return climbStairs(n-1) + climbStairs(n-2);

    }
    
    // 数组实现 a[n]=a[n-1]+a[n-2]
    public int climbStairs(int n) { 
        if(n<3){
            return n;
        }   
        int num[] = new int[n+1];
        num[1]=1;
        num[2]=2;
        for(int i=3;i<=n;i++){
            num[i]=num[i-1] + num[i-2];
        }

        return num[n];
    }

    // 题目仅返回最终结果枚举数量,这里可以只做过程计数
    // fn = f1 + f2
    // f1 = f2
    // f2 = fn
    public int climbStairs(int n) {
        if(n<3){
            return n;
        }

        int f1 = 1;
        int f2 = 2;
        int fn = 0;
        for(int i=3;i<=n;i++){
            fn = f1+f2;
            f1 = f2;
            f2 = fn;
        }
        return fn;
    }
}

两数之和 1. 两数之和

class Solution {
   	// 两层循环暴力求解
    public int[] twoSum(int[] nums, int target) {
        int result[] = new int[2];
        for(int i = 0; i< nums.length -1; i++){
            for(int j = i+1 ; j < nums.length; j++){
                if(nums[i]+ nums[j] == target){
                    result[0]=i;
                    result[1]=j;
                }
            }
        }
    }

    // <target - nums[i], i> 入Map,从Map中获取到index
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap();
        for(int i = 0;i< nums.length;i++){
            if(map.containsKey(target - nums[i])){
                return new int[]{map.get(target - nums[i]), i};
            }
            map.put(nums[i],i);
        }

        return new int[0];
    }

    // 使用双指针,当前实现无法走通,排序后的nums数组坐标就不对了
    public int[] twoSum(int[] nums, int target) {
        Arrays.sort(nums);
        int i = 0, j = nums.length-1;

        while(i!=j){
            if(nums[i]>target) break;
            int sum = nums[i]+nums[j];

            if(sum < target){
                while(i!=j && nums[i]==nums[++i]) i++;
            } else if(sum>target){
                 while(i!=j && nums[j]==nums[--j]) j--;
            } else {
                return new int[]{i,j};
            }
        }
        return new int[0];
    }
    // 双指针法 记录排序前的数组 双指针找到结果后 到排序前的数组中还原
    // 注意特殊的用例 nums=[3,3] target=6
    public int[] twoSum(int[] nums, int target) {
        int m=0,n=0,k,board=0;
        int[] res=new int[2];
        int[] tmp1=new int[nums.length];
        System.arraycopy(nums,0,tmp1,0,nums.length);
        Arrays.sort(nums);
        for(int i=0,j=nums.length-1;i<j;){
            if(nums[i]+nums[j]<target)
                i++;
            else if(nums[i]+nums[j]>target)
                j--;
            else if(nums[i]+nums[j]==target){
                m=i;
                n=j;
                break;
            }
        }
        for(k=0;k<nums.length;k++){
            if(tmp1[k]==nums[m]){
                res[0]=k;
                break;
            }
        }
        for(int i=0;i<nums.length;i++){
            if(tmp1[i]==nums[n]&&i!=k)
                res[1]=i;
        }
        return res;
    }
}

双指针法处理3数之和

class Solution {
    // 三层循环
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> result = new HashSet<List<Integer>>();
        for(int i = 0;i<nums.length -2;i++){
            for(int j=i+1;j<nums.length-1;j++){
                for(int k=j+1;k<nums.length;k++){
                    if(nums[i]+nums[j]+nums[k]==0){
                        List<Integer> res = Arrays.asList(nums[i],nums[j],nums[k]);
                        Collections.sort(res);
                        result.add(res);
                    }
                }
            }
        }
        return new ArrayList(result);
    }

    // 哈希表法 待实现
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> result = new HashSet<List<Integer>>();
        Map<Integer,List<Integer>> map = new HashMap();
        for(int i = 0;i< nums.length-1;i++){
            for(int j=i+1;j<nums.length;j++){
                if(map.containsKey(-nums[i]-nums[j])){
                    List<Integer> res = new ArrayList();
                    res.addAll(map.get(-nums[i]-nums[j]));
                    res.add(-nums[i]-nums[j]);
                    Collections.sort(res);
                    result.add(res);
                }
                map.put(-nums[i]-nums[j],Arrays.asList(nums[i],nums[j]));
            }
        }
    }
 
    // 双指针法
    // 算法说明 https://leetcode-cn.com/problems/3sum/solution/3sumpai-xu-shuang-zhi-zhen-yi-dong-by-jyd/
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> result = new HashSet<List<Integer>>();
        Arrays.sort(nums);
        for(int k=0;k<nums.length-2;k++){
            if(nums[k]>0) break;
            if(k>1 && nums[k]==nums[k-1]){
                continue;
            }
            int i=k+1;
            int j=nums.length-1;
            while(i<j){
                int sum = nums[k]+nums[i]+nums[j];
                if(sum<0){
                    while(i<j && nums[i]==nums[++i]);
                } else if(sum>0){
                    while(i<j && nums[j]==nums[--j]);
                } else {
                    result.add(Arrays.asList(nums[k],nums[i],nums[j]));
                    while(i<j && nums[i]==nums[++i]);
                    while(i<j && nums[j]==nums[--j]);
                }
            }
        }

        return new ArrayList(result);
    }
}

精选链表操作

单链表反转 206. 反转链表

Reverse a singly linked list.

Example:

Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL

class Solution {
    /**
    * 解题思路:
    * prev定义作为前节点,同时作为新链的头结点
    * pos不断的移动,保证原链表中的剩余数据仍然是一个链,不会丢失
    * index作为临时变量,接受从原链中剪掉的数据节点,将其next指向prev,同时作为新链的头结点
    */
    public ListNode reverseList(ListNode head) {
       
        ListNode prev = null;
        ListNode pos = head;

        while(pos!=null){
            ListNode index = pos;
            pos=pos.next;
            index.next = prev;
            prev=index;
        }
        return prev;
    }
       
    /**
    * 解题思路:
    * pos不断的移动,保证原链表中的剩余数据仍然是一个链,不会丢失
    * newHead构造新链,将原链中剪掉头部的数据不断的在新头部插入
    * index作为临时变量,接受从原链中剪掉的数据节点
    */
     public ListNode reverseList(ListNode head) {   
        ListNode newHead = head;
        ListNode pos = head;
        while(pos!=null){
            ListNode index=pos;
            pos=pos.next;
            if(index == head){
                newHead = index;
                newHead.next =null;
            } else{
                index.next = newHead;
                newHead = index;
            }            
        }
    return newHead;
    
    }
}

按照数据删除节点 剑指 Offer 18. 删除链表的节点

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    /**
    * 实现思路:
    * 1)如果是头结点,直接把头结点往后挪一个,返回新的链
    * 2)如果是尾结点,直接把为节点设置为空
    * 3)如果是中间结点,把上一个结点的next指向下一个结点
    */
    public ListNode deleteNode(ListNode head, int val) {
        if(head.val ==val){
            head=head.next;
            return head;
        }

        ListNode pos = head;
        while(pos!=null){
           if(pos.next!=null){
               if(pos.next.val == val){
               pos.next=pos.next.next;
               }
               pos=pos.next;
           } else{
               pos=null;
           }
        } 

        return head;
    }
    /**
    * 实现思路:使用哨兵结点
    * 使用哨兵结点后,相当于指针可以往前挪一个位置
    * 1)如果是头结点,直接把哨兵结点连到头结点的下一个结点
    * 2)如果是尾结点,也可以将pre连到为节点的next(即null)
    */
    public ListNode deleteNode(ListNode head, int val) {
      
        ListNode guard = new ListNode(0);
        ListNode pre = guard;
        pre.next = head;

        while(head!=null){
            if(head.val==val){
                pre.next = head.next;
            }

            head = head.next;
            pre = pre.next;
        }

        return guard.next;
    }

}

给出链表的中间某节点,删除他,返回链表 面试题 02.03. 删除中间节点

class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

链表中环的检测 141. 环形链表

/**
* 解题思路:快慢指针,相遇则有环
*/
public class Solution {
    public boolean hasCycle(ListNode head) {

        if(head == null || head.next ==null){
            return false;
        }

        ListNode fast = head.next;
        ListNode slow = head;

        while(slow!=fast){
            if(fast==null||fast.next==null){
                return false;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        return true;
    }
}

/**
* 解题思路:朴素思路
*/
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> visited = new HashSet<>();
        while(head!=null){
            if(!visited.add(head)){
                return true;
            }
            head=head.next;
        }
        return false;
    }
}

环形链表进阶142. 环形链表 II 找到入环结点

public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> visited = new HashSet();
        ListNode pos = head;
        while(pos!=null) {
            if(visited.contains(pos)){
                return pos;
            } 
            visited.add(pos);
            pos = pos.next;
        }
        return null;
    }
	// 同一起点,相遇时,快走路程时慢走路程的2倍
    // a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)⟹起点走到入环点和相遇点走到入环点的距离相等
    // ==> 
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
		//如果快指针走到尽头,没环
        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;

            if(fast == slow) {
                ListNode start = head;
                while(slow != start){
                    start = start.next;
                    slow = slow.next;
                }
                return start;
            }
        }

        return null;
    }
}

两个有序的链表合并 21. 合并两个有序链表

class Solution {

    /**
    * 解题思路:
    * 循环链表l1和l2的每个节点,比较
    * 如果l1的节点比l2的小,就插入到新链中去,同时把l1的节点往下走,反之亦然
    * 最后如果某个链还有剩余节点,就表示是大的,直接追加到新链后面去
    */
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode newHead = new ListNode();
        ListNode index = newHead;
        while(l1!=null && l2!=null){
            ListNode temp1 = l1;
            ListNode temp2 = l2;
            if(temp1.val<=temp2.val){
                index.next = temp1;
                index = index.next;
                l1 = l1.next;
            } else {
                index.next = temp2;
                index = index.next;
                l2 = l2.next;
            }
        }

        if(l1!=null){
            index.next = l1;
        } else {
            index.next = l2;
        }

        return newHead.next;
    }
    
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode();
        ListNode temp = dummy;
        ListNode pos1 = l1;
        ListNode pos2 = l2;
        while(pos1!=null && pos2!=null){
            if(pos1.val < pos2.val){
                temp.next = pos1;
                pos1 = pos1.next;
            } else {
                temp.next = pos2;
                pos2 = pos2.next;
            }
            temp = temp.next;
        }

        if(pos1!=null){
            temp.next = pos1;
        } else {
            temp.next = pos2;
        }

        return dummy.next;
    }
}

找到链表倒数第n个节点 剑指 Offer 22. 链表中倒数第k个节点

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    /**
    * 解题思路:朴素思路
    * 先遍历,然后算n-k+1
    */
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode pos = head;
        int count = 0;
        while(pos!=null){
            count++;
            pos = pos.next;
        }

        int seq = count -k +1;
        pos = head;
        for(int i=1;i<seq;i++){
            pos = pos.next;
        }
        return pos;
    }

    /**
    * 解题思路:双指针法
    * 两个指针,第一个先走k-1步,第二个保持在头上,这样第一个走到头,第二个正好在到时第k个
    */
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode pos = head;
        ListNode fast = head;
        for(int i=0;i<k-1;i++){
            fast = fast.next;
        }

        while(fast.next!=null){
            fast=fast.next;
            pos=pos.next;
        }

        return pos;
    }

}

删除链表倒数第n个结点

求链表的中间结点 876. 链表的中间结点

/**
* 解题思路:快慢指针,一1一2
*/
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast!= null && fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
}

/**
* 解题思路:朴素算法
*/
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode pos = head;
        int count = 0;
        while(pos!=null){
            count++;
            pos = pos.next;
        }
        double middle = count / 2.0 +0.5;
        int seq = new Double(Math.ceil(middle)).intValue();
        pos = head;
        for(int i=1;i<seq;i++){
            pos = pos.next;
        }
        return pos;
    }
}

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

class Solution {
    // 题解参考
    // https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/liang-liang-jiao-huan-lian-biao-zhong-de-jie-di-91/
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode();
        dummy.next = head;
        ListNode temp = dummy;

        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 = node2.next;
        }
        return dummy.next;
    }
}

k个节点一组,逆序 25. K 个一组翻转链表

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode();

        dummy.next = head;
        // pre固定k节点逆序前的指针,这样k逆序后还能和前面的连上
        ListNode pre = dummy;
        // end固定k节点的结尾,要断掉结尾
        ListNode end = dummy;

        while(end.next!=null){
            for (int i = 0; i < k && end != null; i++) end = end.next;
            if (end == null) break;
            // start固定每次k逆序的头节点
            ListNode start = pre.next;
            // next记录断尾的后一个节点,方便后面连上
            ListNode next = end.next;
            end.next = null;
            pre.next = reverseList(start);
            // 逆序后start是k的最后一个节点,需要链接到next
            start.next = next;
            pre = start;
            end = pre;
        }
        
        return dummy.next;
    }

    private ListNode reverseList(ListNode head){

        ListNode newHead = null;
        ListNode pos = head;

        while(pos!=null){
            ListNode temp = pos;
            pos=pos.next;
            temp.next = newHead;
            newHead = temp;
        }
        return newHead;
    }
}
posted @ 2021-02-15 19:53  sxchen2012  阅读(59)  评论(0)    收藏  举报