目录
目录
目录
全部题目链接地址
[简单]剑指 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>
集合中。让我们来解释这段代码的每个部分:
s
是一个包含字符串的变量,你需要在使用这段代码前先给它赋值。
split("\\s+")
是对字符串s
进行分割的操作。split()
方法接受一个正则表达式作为参数来指定分隔符。在这里,我们使用了正则表达式\\s+
来表示一个或多个空白字符。这样,split("\\s+")
会将字符串s
按照空白字符进行分割,并返回一个字符串数组。
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);}
}
效果