【剑指offer】双指针7题 全刷(详解)

目录

目录

目录

全部题目链接地址

[简单]剑指 Offer 18. 删除链表的节点

题目

方法

[简单]剑指 Offer 22. 链表中倒数第k个节点

题目

方法:双指针距离法

 [简单]剑指 Offer 25. 合并两个排序的链表

题目

方法:双指针

[简单]剑指 Offer 52. 两个链表的第一个公共节点

题目

方法1:栈(不华丽) 

方法2:双指针浪漫相遇

[简单]剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

题目

方法1:双指针

 方法2:双指针优化

 [简单]剑指 Offer 57. 和为s的两个数字

题目 

方法1:双指针-嵌套for循环

 方法2:双指针-先相加再比较

[简单]剑指 Offer 58 - I. 翻转单词顺序 

题目

 方法1:双指针栈

 方法2:集合翻转法


全部题目链接地址

剑指 Offer - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

[简单]剑指 Offer 18. 删除链表的节点

题目

单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

注意:此题对比原题有改动

题目地址:剑指 Offer 18. 删除链表的节点 - 力扣(LeetCode) 

方法

思路

删除一个节点其实就是让这个节点的上一个节点 指向 这个节点的下一个节点即可

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode p = head;
        if(p.val == val)return head.next;
        while(p.next != null){
            if(p.next.val == val){
                p.next = p.next.next;
                return head;
            }
            p = p.next;
        }
        return head;
 
    }
}

 效果

[简单]剑指 Offer 22. 链表中倒数第k个节点

题目

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

题目地址:剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode)  

方法:双指针距离法

思路

定义双指针,一左一右,左右的距离为k,也就是从左指针数到右指针数k个节点,让这两个指针一起往后走,右指针走到底就返回左指针即可。

例如

 

 

 

 代码

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode left = head;
        ListNode right = head;
        while (k > 1){
            right = right.next;
            k--;
        }
        while (right.next!=null){
            right = right.next;
            left = left.next;
        }
        return left;
    }
}

效果

 [简单]剑指 Offer 25. 合并两个排序的链表

题目

入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

本题与主站 21 题相同:力扣

题目地址:剑指 Offer 25. 合并两个排序的链表 - 力扣(LeetCode) 

方法:双指针

思路

简单的思路,就是两个指针分别指向两个链表,哪个的val小就取哪个。

class Solution {
 public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode p1 = l1;
        ListNode p2 = l2;
 
        ListNode res = new ListNode(-1);
        ListNode p = res;
        while (p1!=null && p2!=null){
            if (p1.val <= p2.val){
                p.next = p1;
                p1 = p1.next;
            }else {
                p.next = p2;
                p2 = p2.next;
            }
            p = p.next;
 
        }
        if (p1 == null){
            p.next = p2;
        }else if (p2 == null){
            p.next = p1;
        }
        return res.next;
    }
}

效果

[简单]剑指 Offer 52. 两个链表的第一个公共节点

题目

输入两个链表,找出它们的第一个公共节点。

如下面的两个链表

在节点 c1 开始相交。

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

题目地址:剑指 Offer 52. 两个链表的第一个公共节点 - 力扣(LeetCode)

与另一题相同:160. 相交链表 - 力扣(LeetCode) 

方法1:栈(不华丽) 

思路

思路很简单,将两个链表分别入栈,然后出栈去比较栈顶元素,有交点的两个链表他们入栈之后的栈顶肯定是相同的,那么交点就在第一个不相同的栈顶元素的next。

代码

class Solution {


    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA==null || headB==null)return null;
        Stack<ListNode> stackA = new Stack<>();
        Stack<ListNode> stackB = new Stack<>();
        ListNode p1 = headA;
        ListNode p2 = headB;
        while (p1!=null){
            stackA.add(p1);
            p1 = p1.next;
        }
        while (p2!=null){
            stackB.add(p2);
            p2 = p2.next;
        }
        //最后两个节点不相同,那就是没有相交的节点
        if (stackA.peek()!= stackB.peek() )return null;
        while (!stackA.isEmpty() && !stackB.isEmpty()){
            if (stackA.peek()==stackB.peek()){
                stackA.pop();
                stackB.pop();
                continue;
            }else{
                return stackA.peek().next;
            }
        }
        //既然代码走到了这里,说明不是没有交点
        // 而是这两个栈的其中一个已经空了,那么没空的那个栈的栈顶元素.next就是交点
        //或者两个栈都空了
        if (stackA.isEmpty() && !stackB.isEmpty()){
            return stackB.peek().next;
        }else if (!stackA.isEmpty() && stackB.isEmpty()){
            return stackA.peek().next;
        }else {
            return headA;
        }
    }



}

 这是我自己想出来的方法,过肯定能过,但是不算一个好方法。

方法2:双指针浪漫相遇

思路来自题解

剑指 Offer 52. 两个链表的第一个公共节点 - 力扣(LeetCode)

思路

思路很简单,定义两个指针分别指向两个链表的头结点,然后两个指针同步往后走,每次都比较看是否相同,如果某个指针走完了自己的链表,那就开始走另一个链表,这样总能相遇,相遇的那个节点就是相交节点。

例如:没有交点的情况,最后都会走到null,null==null,所以就返回了null,正确

再来看有交点的情况

 如果有交点且两个链表长度相同,则两指正同步走直接就能比对出来

代码

class Solution {


    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA==null||headB==null)return null;
        ListNode p1 = headA;
        ListNode p2 = headB;
        while (true){
            if (p1 == p2)return p1;
            if (p1 == null){
                p1 = headB;
                continue;
            }
            if (p2 == null){
                p2 = headA;
                continue;
            }
            p1 = p1.next;
            p2 = p2.next;
        }
    }

}

 效果就比较好

[简单]剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

题目

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 - 力扣(LeetCode)

方法1:双指针

思路

两个指针,一左一右,左边找到偶数,右边找到奇数,调换位置即可

代码

class Solution {
    public int[] exchange(int[] nums) {
        int len = nums.length;
        if (len == 0)return nums;
        int right = len-1;
        int left = 0;
        while (left < right){
            //左边找到偶数
            if (isOddNumber(nums[left])){
                left++;
                continue;
            }
            //右边找到奇数
            if (!isOddNumber(nums[right])){
                right--;
                continue;
            }
            swap(nums,left,right);
            left++;
            right--;
        }
        return nums;

    }

    private void swap(int[] nums, int i, int p) {
        int index = nums[i];
        nums[i] = nums[p];
        nums[p] = index;
    }

    /**
     * 是奇数
     */
    boolean isOddNumber (int num){
        if (num % 2 == 0){
            return false;
        }
        return true;
    }

}

效果

 方法2:双指针优化

可以看到方法1不是很优秀

我们优化一下代码,思路是一样的

优化:

1. 将两个if 换成 while

2. 将 %2 替换成  &1 ,效果一样,但是后者效率更高

class Solution {
    public int[] exchange(int[] nums) {
        int len = nums.length;
        if (len == 0)return nums;
        int right = len-1;
        int left = 0;
        while (left < right){
            //左边找到偶数
            while (left<right && (nums[left] & 1) == 1){
                left++;
            }
            //右边找到奇数
            while (left<right  && (nums[right] & 1) == 0){
                right--;
            }
        //    //左边找到偶数
        //    while (left<right && nums[left] % 2 == 1){
        //        left++;
        //    }
        //    //右边找到奇数
        //    while (left<right  && nums[right] % 2 == 0){
        //        right--;
        //    }
            swap(nums,left,right);
            left++;
            right--;
        }
        return nums;

    }

    private void swap(int[] nums, int i, int p) {
        int index = nums[i];
        nums[i] = nums[p];
        nums[p] = index;
    }



}

效果

 [简单]剑指 Offer 57. 和为s的两个数字

题目 

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]

示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

剑指 Offer 57. 和为s的两个数字 - 力扣(LeetCode)

方法1:双指针-嵌套for循环

思路

从后往前找,如果 target>数>=target/2 才能做两个数中大的那个 

找到大的数,开始往前循环找小的数

代码

class Solution {
    //从后往前找,如果 target>数>=target/2 才能做两个数中大的那个
    //找到大的数,开始往前循环找小的数
    public int[] twoSum(int[] nums, int target) {
        int arr[] = new int[2];
        for (int i = nums.length-1; i >-1 ; i--) {
            if (nums[i] < target && nums[i]>=target/2){
                for (int j = i; j > -1 ; j--) {
                    if (nums[j] ==target - nums[i]){
                        arr[0] = nums[j];
                        arr[1] = nums[i];
                        return arr;
                    }
                }
            }
        }
        return arr;
    }





}

效果

 方法2:双指针-先相加再比较

思路

作者:Krahets
链接:https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/solutions/164083/mian-shi-ti-57-he-wei-s-de-liang-ge-shu-zi-shuang-/

方法1是,先把一个数和target比较,再和另一个数加起来再比对

这样的话需要比较两次,而且是两层循环

方法2是,先加起来,再比较,而且是一层循环

算法流程

 代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int i = 0, j = nums.length - 1;
        while(i < j) {
            int s = nums[i] + nums[j];
            if(s < target) i++;
            else if(s > target) j--;
            else return new int[] { nums[i], nums[j] };
        }
        return new int[0];
    }
}

效果

[简单]剑指 Offer 58 - I. 翻转单词顺序 

题目

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。

示例 1:

输入: "the sky is blue"
输出: "blue is sky the"

示例 2:

输入: "  hello world!  "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:

输入: "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

剑指 Offer 58 - I. 翻转单词顺序 - 力扣(LeetCode)

 方法1:双指针栈

思路

因为要颠倒顺序输出,所以我想到用栈,因为栈是先入后出。

然后用双指针分别指向一个单词的前后,放到栈里面就行了,最后再从栈里取出来。

代码流程

1.从左往右遍历字符串,找到非空格字符,用指针 l 记录位置

2.从 l 开始遍历寻找一个单词的最后一个字符,找到后,用指针 r 记录位置

3.将 [ l , r ]的数据入栈。

4.出栈拼接字符串

代码

class Solution {
      /**
     *  1.从左往右遍历字符串,找到非空格字符,用指针 l 记录位置
     *  2.从 l 开始遍历寻找一个单词的最后一个字符,找到后,用指针 r 记录位置
     *  3.将 [ l , r ]的数据入栈。
     *
     */
    public String reverseWords(String s) {
        int len = s.length();
        Stack<String> stack = new Stack<>();

        for (int i = 0; i < len; i++) {
            if (s.charAt(i) !=' '){
                for (int j = i; j < len; j++) {
                    if (j+1 >= len || s.charAt(j + 1) == ' '){
                        //入栈 [i , j]
                        stack.add(s.substring(i,j+1));
                        i = j;
                        break;
                    }
                }
            }
        }
        String res = "" ;
        while (!stack.isEmpty()){
            res = res.concat(stack.pop());
            if (stack.isEmpty())break;
            res = res.concat(" ");
        }
        return res;


    }
}

效果

 方法2:集合翻转法

思路

先把字符串前后空格去掉,再把字符串按照一个或多个空格切分开,再放入到 list 集合中,再翻转这个集合就行。

思路简单,但是这些方法如何使用需要我们学习。

下面这行代码详解

List<String> wordList = Arrays.asList(s.split("\\s+"));

代码使用了Java的split()方法将一个字符串 s 按照空白字符(例如空格、制表符、换行符等)进行分割,并将结果存储到一个List<String>集合中。

让我们来解释这段代码的每个部分:

  1. s 是一个包含字符串的变量,你需要在使用这段代码前先给它赋值。

  2. split("\\s+") 是对字符串 s 进行分割的操作。split() 方法接受一个正则表达式作为参数来指定分隔符。在这里,我们使用了正则表达式 \\s+ 来表示一个或多个空白字符。这样,split("\\s+") 会将字符串 s 按照空白字符进行分割,并返回一个字符串数组。

  3. Arrays.asList(...) 将得到的字符串数组转换为List<String>集合。Arrays.asList(...) 是一个静态方法,它接受一个数组作为参数,并返回一个List集合。

综合起来,这段代码的作用是将一个字符串 s 按照空白字符进行分割,并将分割后的结果存储到List<String>类型的wordList集合中。

例如,假设 s 的值为:"Hello World Java",那么wordList将会是一个包含三个元素的List集合:["Hello", "World", "Java"]。

代码

class Solution {
    public String reverseWords(String s) {
        // 除去开头和末尾的空白字符
        s = s.trim();
        // 正则匹配连续的空白字符作为分隔符分割
        List<String> wordList = Arrays.asList(s.split("\\s+"));
        Collections.reverse(wordList);
        return String.join(" ", wordList);
    }
}

效果 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值