#JAVA #REVIEW
开源学习资料
Feeling and experiences:
今日复习之前的知识,双指针的用法
根据代码来复习与回顾:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
class Solution {
public int removeElement(int[] nums, int val) {
// 快慢指针
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] != val) {
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
}
此题运用到的是快慢指针,其本质是要完成元素的移动,后面覆盖前面。
模拟:最开始慢指针和快指针都指向第一个元素,如果该元素不等于val,则一起移动。
当出现了指向的元素等于val,这时候慢指针会停下来,而快指针则继续遍历(注意:此时慢指针指向的元素==val)
而快指针在遍历的时候,遇到不等于val的元素就会把这个元素给慢指针。
所以,当我们遍历完,慢指针指向的元素一定不等于val,返回慢指针,就相当于返回了移除元素后的数组长度。(本题很简单,具体可以手动模拟)
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
class Solution {
public void reverseString(char[] s) {
int l = 0;
int r = s.length - 1;
while(l < r){
char temp = s[l];
s[l] = s[r];
s[r] = temp;
l++;
r--;
}
}
}
本题就更加就简单了,也是昨天的一道题,简单的交换,相向双指针的运用。
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。
例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String s = in.nextLine();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (Character.isDigit(s.charAt(i))) {
sb.append("number");
}else sb.append(s.charAt(i));
}
System.out.println(sb);
}
}
本题有些特殊,可以用到StringBuilder就非常简单,只要把元素依次append到该类的对象中,遇到数字就替换添加成“number”即可。
因为Java不能直接对字符串进行处理,必须用辅助空间。
所以在Java中用不到双指针。
在C/C++中,运用双指针,可以在字符串后开辟空间,然后从后向前替换数字字符。
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
class Solution {
public String reverseWords(String s) {
// trim方法 删除首尾空格
s = s.trim();
int j = s.length() - 1, i = j;
StringBuilder res = new StringBuilder();
while (i >= 0) {
while (i >= 0 && s.charAt(i) != ' ') i--;
res.append(s.substring(i + 1, j + 1) + " ");
while (i >= 0 && s.charAt(i) == ' ') i--;
j = i;
}
return res.toString().trim();
}
}
该题在做的时候出现了很多次错误, 最好还是手动模拟。
用到的两个指针,我感觉像是在你追我赶,一个指针记录记录先走的那个指针的出发位置,等那个指针停下了,记录了结果就直接瞬移过去。(反复这样执行)
题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
ListNode temp = null;
while (cur != null) {
temp = cur.next;// 保存下一个节点
cur.next = prev;
prev = cur;
cur = temp;
}
return prev;
}
}
这道题也可以形象理解为你追我赶,但要注意的是,为了防止节点的丢失,要用临时变量来保存
int getLength(struct ListNode* head) {
int length = 0;
while (head) {
++length;
head = head->next;
}
return length;
}
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* dummy = malloc(sizeof(struct ListNode));
dummy->val = 0, dummy->next = head;
int length = getLength(head);
struct ListNode* cur = dummy;
for (int i = 1; i < length - n + 1; ++i) {
cur = cur->next;
}
cur->next = cur->next->next;
struct ListNode* ans = dummy->next;
free(dummy);
return ans;
}
该题最开始想到的不是用双指针,而是遍历两次链表,第一次找链表的长度,第二次再来处理要删除的节点。
而双指针的用法,一个指针从头往后遍历,一个指针指向null。
这样就使得在操作的时候不用遍历两次链表了。
题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
//先设置一个快指针,和一个慢指针
struct ListNode* fast = head;
struct ListNode* slow = head;
//找到slow , fast 相遇点
while(fast != NULL && fast->next != NULL) {
//设置它们的速度,fast一下走两节点,slow一下走一个节点
fast = fast->next->next;//代表速度为2
slow = slow->next;//代表速度为1
if(fast == slow){
//找到了相遇点
//则从这个相遇点设置一个指针速度为1,头节点再设置一个指针速度为1,让它们开始运动
struct ListNode* index01 = fast;
struct ListNode* index02 = head;
//当这两个指针相遇了,则该相遇点为入口
while(index01 != index02){
index01 = index01->next;
index02 = index02->next;
}
return index01;
}
}
return NULL;
}
这是一道非常有趣的题,巧妙运用了数学知识,解决了跳出循环的问题。
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
class Solution {
public List> threeSum(int[] nums) {
List> result = new ArrayList<>();
Arrays.sort(nums);
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.length; i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
//当前三个数的和大于0了,则让右指针左移
if (sum > 0) {
right--;
//小了就让左指针右移
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}
需要注意的就是去重问题,还有剪枝。
题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
class Solution {
public List> fourSum(int[] nums, int target) {
//创建一个集合来收集满足条件的答案
List> list = new ArrayList<>();
int len = nums.length;
//对数组进行升序排列
Arrays.sort(nums);
for(int i = 0;i < len;i++){
//特殊情况进行判断
if(nums[i]>0 && nums[i]>target){
return list;
}
//对a进行去重操作
if(i>0 && nums[i-1] == nums[i]){
//如果后一个数与前一个数相同,则跳过该数
continue;
}
for(int j = i+1;j < len;j++){
// 第二层循环也如此做
if (j>i+1 && nums[j - 1] == nums[j]) { // 对nums[j]去重
continue;
}
//设置双指针:左指针left。右指针right
int left = j+1;
int right = len-1;
while(lefttarget){
//说明大了
right--;
}
else if(sum
此题的思路和三数之和的思路一样。
注意:这两道题,没有用到map集合来做的原因就是题目要求三元or四元组不能重复,如果用map集合,键值对匹配来做的话,就太过于繁琐了。
总结:
在什么时候会用到双指针的思想?
结合以上练习的题目,可以归纳出:
在数组中:有很多要求原地移除,交换的题目,大多且基本都可以用到双指针,这样可以大大提高效率,减少for循环的嵌套。
在链表中:环形链表这类题中,双指针的运用可以满足条件跳出循环,找到出口,也就是可以用在找环形出口上。
滑动窗口:经典的双指针运用,快慢指针的配合。
组合问题:用来组合匹配寻找目标值。
在代码随想录中,有更详细的总结:双指针总结篇
慢品人间烟火色
闲观万事岁月长 ~
Fighting!