目录
目录
全部题目链接地址
[简单]剑指 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. 删除链表的节点 - 力扣(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;
}
}
效果
输入一个链表,输出该链表中倒数第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;
}
}
效果
入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例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;
}
}
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
在节点 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)
思路
思路很简单,将两个链表分别入栈,然后出栈去比较栈顶元素,有交点的两个链表他们入栈之后的栈顶肯定是相同的,那么交点就在第一个不相同的栈顶元素的next。
代码
class Solution {
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA==null || headB==null)return null;
Stack stackA = new Stack<>();
Stack 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;
}
}
}
这是我自己想出来的方法,过肯定能过,但是不算一个好方法。
思路来自题解
剑指 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;
}
}
}
效果就比较好
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。
示例:
输入:nums = [1,2,3,4] 输出:[1,3,2,4] 注:[3,1,2,4] 也是正确的答案之一。
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 - 力扣(LeetCode)
思路
两个指针,一左一右,左边找到偶数,右边找到奇数,调换位置即可
代码
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;
}
}
效果
可以看到方法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
效果
输入一个递增排序的数组和一个数字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)
思路
从后往前找,如果 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;
}
}
效果
思路
作者: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];
}
}
效果
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"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.从左往右遍历字符串,找到非空格字符,用指针 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 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;
}
}
效果
思路
先把字符串前后空格去掉,再把字符串按照一个或多个空格切分开,再放入到 list 集合中,再翻转这个集合就行。
思路简单,但是这些方法如何使用需要我们学习。
下面这行代码详解
List
wordList = Arrays.asList(s.split("\\s+")); 代码使用了Java的
split()
方法将一个字符串s
按照空白字符(例如空格、制表符、换行符等)进行分割,并将结果存储到一个List
集合中。让我们来解释这段代码的每个部分:
s
是一个包含字符串的变量,你需要在使用这段代码前先给它赋值。
split("\\s+")
是对字符串s
进行分割的操作。split()
方法接受一个正则表达式作为参数来指定分隔符。在这里,我们使用了正则表达式\\s+
来表示一个或多个空白字符。这样,split("\\s+")
会将字符串s
按照空白字符进行分割,并返回一个字符串数组。
Arrays.asList(...)
将得到的字符串数组转换为List
集合。Arrays.asList(...)
是一个静态方法,它接受一个数组作为参数,并返回一个List集合。综合起来,这段代码的作用是将一个字符串
s
按照空白字符进行分割,并将分割后的结果存储到List
类型的wordList
集合中。例如,假设
s
的值为:"Hello World Java",那么wordList
将会是一个包含三个元素的List集合:["Hello", "World", "Java"]。
代码
class Solution {
public String reverseWords(String s) {
// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
List wordList = Arrays.asList(s.split("\\s+"));
Collections.reverse(wordList);
return String.join(" ", wordList);
}
}
效果