27. 移除元素
思路:双指针,一个指针(right)用来遍历元素:
- 当元素不等于要删除元素时,将right指针指向的元素赋值给left指针指向的元素,然后left指针往前移动。
- 当元素等于要删除元素时,left指针不动这样下次情况为1时就会把删除元素给覆盖掉了! 遍历完后,left指针所在位置就是新数组长度!
public int removeElement(int[] nums, int val) {
int left=0;
for(int right=0;right<nums.length;right++){
if(nums[right]!=val){
nums[left]=nums[right];
left++;
}
}
return left;
}
344. 反转字符串
思路:双指针,然后使用位运算
public void reverseString(char[] s) {
int start=0;
int end=s.length-1;
while(start<end){
s[start]^=s[end];
s[end]^=s[start];
s[start]^=s[end];
start++;
end--;
}
}
剑指 Offer 05. 替换空格
思路:这题除了双指针也可以用StringBuilder会让代码更简洁
//使用StringBuilder
public String replaceSpace(String s) {
StringBuilder sb=new StringBuilder();
for(char ch : s.toCharArray()){
if(ch==' '){
sb.append("%20");
}else
sb.append(ch);
}
return sb.toString();
}
//双指针
public String replaceSpace(String s) {
if(s.length()==0 || s==null)
return s;
StringBuilder sb=new StringBuilder();
for(int i=0;i<s.length();i++){
if(s.charAt(i)==' ')
sb.append(" ");//新增加2个位置
}
int left=s.length()-1; //指向原来s的末尾
s+=sb.toString();
int right=s.length()-1;//指向新s的末尾
char[] ch=s.toCharArray();
//从后往前填充元素,这样才不会覆盖掉其他元素
while(left>=0){
if(ch[left]==' '){
ch[right--]='0';
ch[right--]='2';
ch[right]='%';
}else{
ch[right]=ch[left];
}
left--;
right--;
}
return new String(ch);
}
151. 反转字符串中的单词
151. 反转字符串中的单词 思路:交换字符串用到双指针。
- 去除所有多余空格整理出新的句子
- 先反转整个句子,再逐个反转里面的单词
- 反转每个单词时,注意遍历的范围判断要用start!!如果用end的话,最后一个单词就会因为超出范围跳出循环从而没进行到交换!所以这个细节很重要
public String reverseWords(String s) {
StringBuilder sb=removeSpace(s);
//整个反转
reverse(sb,0,sb.length()-1);
//反转每个单词
reverseString(sb);
return sb.toString();
}
public StringBuilder removeSpace(String s){
//去除多余空格
int start=0;
while(s.charAt(start)==' ')
start++;
int end=s.length()-1;
while(s.charAt(end)==' ')
end--;
StringBuilder sb = new StringBuilder();
while(start<=end){
char c = s.charAt(start);
if(c!=' ' || sb.charAt(sb.length()-1)!=' ')
sb.append(c);
start++;
}
return sb;
}
//反转给定范围内的字符串
public void reverse(StringBuilder sb,int start,int end){
while(start<end){
char tmp=sb.charAt(start);
sb.setCharAt(start,sb.charAt(end));
sb.setCharAt(end,tmp);
start++;
end--;
}
}
//反转每个单词
public void reverseString(StringBuilder sb){
int start=0;
int end=start+1;
while(start<sb.length()){
while(end<sb.length() && sb.charAt(end)!=' ')
end++;
reverse(sb,start,end-1);
start=end+1;
end=start+1;
}
}
206. 反转链表
206. 反转链表 思路:建议画图才好理解,画出连接关系(next)。
要断开当前元素next指向关系前,需要记录当前元素next的下一个元素!这样才好接着遍历下去,不会断开。
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode tmp=cur.next;
cur.next=pre;
pre=cur;
cur=tmp; //移动
}
return pre;
19. 删除链表的倒数第 N 个结点
思路:使用快慢指针,涉及到删除所以用到dummy,注意这里快慢指针都先指向dummy。
- 根据删除倒数第
n个节点,快指针先走n+1步(因为一开始指向dummy所以+1) - 快慢指针再一起遍历,直到快指针走完链表
- 此时慢指针来到要删除的节点前一个位置
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy=new ListNode();
ListNode fast=dummy;
ListNode slow=dummy;
dummy.next=head;
while(n>=0){
fast=fast.next;
n--;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummy.next;
}
面试题 02.07. 链表相交
思路:
- 两个链表先各自遍历计算长度,自己规定
链表A永远代表较长的链表(方便后续计算),所以计算完后如果当前链表A较短要做对调操作。 - 两个长度相减得到差值,让
链表A先走这么多步,这样两个链表的起点位置就相同了 - 开始遍历链表找出相同节点就是相交处
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode nodeA=headA;
ListNode nodeB=headB;
int lA=0;
int lB=0;
while(nodeA!=null){
nodeA=nodeA.next;
lA++;
}
while(nodeB!=null){
nodeB=nodeB.next;
lB++;
}
nodeA=headA;
nodeB=headB;
if(lA<lB){
int tmp=lA;
lA=lB;
lB=tmp;
nodeA=headB;
nodeB=headA;
}
int num=lA-lB;
while(num>0){
nodeA=nodeA.next;
num--;
}
while(nodeA!=null){
if(nodeA==nodeB){
return nodeA;
}
nodeA=nodeA.next;
nodeB=nodeB.next;
}
return null;
}
142. 环形链表 II
思路:主要用快慢指针,快指针永远走2步,慢指针走1步。然后分两部分判断:一是否有循环、二循环口在哪里
-
如果快指针和慢指针相等了,就表示走进循环中
- (此时选定慢指针,让其从当前点继续每次走1步)
- 一个指针从头开始(每次走1步)如果和慢指针相等了就表示当前点就是循环口!
-
没有相等表示没有循环!
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(slow==fast){
ListNode cur=head;
while(cur!=fast){
cur=cur.next;
fast=fast.next;
}
return cur;
}
}
return null;
}
15. 三数之和
思路:不能有重复结果,所以要先对数组进行排序,然后要对当前元素进行判断是否跟之前元素一样,如果是则需要跳过。
- 通过for找到第一个不重复的元素,接下来两个元素分别用两个指针来标识!双指针移动范围:当前元素下一个作为起始,数组末尾元素作为结束。
- 开始计算三个元素相加结果,并进行相应处理
- sum>0:表示right大了,要移动--
- sum<0:表示left小了,要移动++
- =0:找到一组,加入到结果集合中,并对两指针进行移动找到分别找到下一个不重复的元素继续计算。要注意指针移动不能超出范围!
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list=new ArrayList<>();
if(nums.length<3)
return list;
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
if(i>0 && nums[i]==nums[i-1])
continue;
int left=i+1;
int right=nums.length-1;
while(left<right){
int sum=nums[i]+nums[left]+nums[right];
if(sum>0){
right--;
}else if(sum<0){
left++;
}
else{
list.add(new ArrayList<>(Arrays.asList(nums[i],nums[left],nums[right])));
while(left<right && nums[left]==nums[left+1])
left++;
while(left<right && nums[right-1]==nums[right])
right--;
left++;
right--;
}
}
}
return list;
}
18. 四数之和
18. 四数之和 思路:和三数之和差不多,只是多了一个元素,在选取出第一个元素后,再用for循环重复步骤选出第二个元素,其他两个元素一样用指针
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list=new ArrayList<>();
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
if(i>0 && nums[i]==nums[i-1])
continue;
for(int j=i+1;j<nums.length;j++){
if(j>i+1 && nums[j]==nums[j-1])
continue;
int left=j+1;
int right=nums.length-1;
while(left<right){
long sum=(long)nums[i]+nums[j]+nums[left]+nums[right];
if(sum>target){
right--;
}else if(sum<target)
left++;
else{
list.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
while(left<right && nums[left]==nums[left+1])
left++;
while(left<right && nums[right]==nums[right-1])
right--;
left++;
right--;
}
}
}
}
return list;
}