N数之和/差
Array.sort(xxx);
while(终止条件 head<tail)
- num[head]+num[tail]<target head++
- num[head]+num[tail]>target tail--
- num[head]+num[tail]==target head++;tail-- 要找到所有满足感条件的组合就不返回继续移动指针
使用对撞的双指针,一个指向首一个指向尾,不断向中间逼近,先把数组排好序,复杂度O(nlogn),再根据和目标值的关系移动首尾指针,在一个for循环下完成两个for循环的工作。对于三数之和使用双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。
1.两数之和
给定一个整数数组 nums
, 和一个整数目标值 target
,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标
若数组无序且返回下标时不能用双指针,因为双指针需要排序会打乱下标。若给出有序数组则可用双指针法,复杂度为O(Nlogn)(排序复杂度nlogn 循环n)
Hashmap方法
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<numbers.length;i++)
{
if(map.containsKey(target-numbers[i]))
{
return new int[]{map.get(target-numbers[i])+1,i+1};
}
map.put(numbers[i],i);
}
15.三数之和
一个包含 n
个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0
且不重复的三元组。
//与两数之和相比,target为nums[first]。多一个for循环遍历nums[first]
for(first=0;first<nums.length;first++)
{
if(first!=0&&nums[first]==nums[first-1])continue; first不重复
second=first+1;third=nums.length-1;
while(second<third)
{
sum=nums[second]+nums[third];
if(sum==-nums[first])
{
List<Integer> tmp=new ArrayList<Integer>();
tmp.add(nums[first]);tmp.add(nums[second]);tmp.add(nums[third]);
ans.add(tmp);
while(second<third&&nums[second]==nums[second+1])//本题的坑在这儿 在循环内部改变循环变量后要及时判断是否还满足循环条件。也要保证second不重复
second++;
while(second<third&&nums[third-1]==nums[third])//third不重复
third--;
second++;
third--;
}
else if(sum<-nums[first])
{
second++;
}
else if(sum>-nums[first])
{
third--;
}
}
}
给定一个包括 n 个整数的数组 nums
和 一个目标值 target
。找出 nums
中的三个整数,使得它们的和与 target
最接近。返回这三个数的和。假定每组输入只存在唯一答案。
while(second<third)
{
sum 三数和 tmp:与target差距 minDistance:最小差距
int sum=nums[second]+nums[third]+nums[first];
int tmp=Math.abs(sum-target); //
if(tmp<=minDistance) //对遍历到的每个组合判断其是不是符合题目要求
{
result=sum;
minDistance=tmp;
}
if(sum<target) //双指针定义数组的可选范围 不断夹逼target
second++;
else if(sum>target)
third--;
else
return result;
}
return result;
给定两个整数数组a
和b
,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该对数值的差
Arrays.sort(a);
Arrays.sort(b);
int pa=0,pb=0;
long minDiff=Long.MAX_VALUE;
while(pa<a.length&&pb<b.length)
{
long diff=Math.abs((long)a[pa]-(long)b[pb]);
if(diff<minDiff)
{
minDiff=diff;
}
if(a[pa]<b[pb]) //为了使两个数尽可能接近,两个指针你追我赶,这种逼近有一种贪心思想
pa++;
else if(a[pa]>b[pb])
pb++;
else
return (int)minDiff;
}
return (int)minDiff;
}
链表
链表类型的双指针通常是快慢指针
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
ListNode pre=null;
ListNode cur=head;
while(cur!=null)
{
ListNode tmp=cur.next; //这些变量的修改是循环的
cur.next=pre;
pre=cur;
cur=tmp;
}
return pre;
给定一个链表,判断链表中是否有环。
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
设入环前长度为a,环长度为b。 slow每次走一步,fast每次走两步。fast走过的路径长为f,slow走过的长度为s。则第一次相遇时 f=s+nb(fast比slow多走n圈)=2s ----> s=nb.
入环点位于a+nb的位置,所以从第一次相遇的位置开始再走a步即可到入环点,但a是未知的,让另一个指针从头开始走,两指针速度一致,各走a步后在入环点相遇。
public ListNode detectCycle(ListNode head) {
ListNode slow=head,fast=head;
do //这个题目的坑在slow fast必须在同一起点出发,因此用do-while结构
{
if(fast==null||fast.next==null)return null;
slow=slow.next;
fast=fast.next.next;
}while(slow!=fast);
fast=head;
while(slow!=fast) //从第一次相遇的位置开始再走a步即可到入环点 a未知可借助双指针
{
slow=slow.next;
fast=fast.next;
}
return slow;
}
指针a从链表headA开始走,走到头就再重新从headB开始走 。指针b从链表headB开始走,走到头就再重新从headA开始走,这样两指针会在交点处第一次相遇。如果两链表不相交,则pa,pb同时指向两个链表末尾的null;
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pa=headA,pb=headB;
while(pa!=pb)
{
pa=pa==null?headB:pa.next; //这里的坑是不能判断pa.next==null,对于不相交两链表,pa,pb要同时指向null才能退出循环。
pb=pb==null?headA:pb.next;
}
return pa;
}
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode half=divide(head);
ListNode first=head,second=reverse(half);
while(first!=null)
{
if(first.val!=second.val)return false;
first=first.next;
second=second.next;
}
return true;
}
//将链表分为前后两部分,返回后半部分链表的起点,快指针一次走两步,慢指针走一步。
//节点数目为奇数后半部分节点比前半部分多一个正中间那个。
public ListNode divide(ListNode head)
{
ListNode slow=head,fast=head,pre=head;
while(fast!=null&&fast.next!=null)
{
pre=slow;
slow=slow.next;
fast=fast.next.next;
}
pre.next=null; //前半部分末尾节点指向null,这样判断回文时循环条件更简单。
return slow;
}
//反转后半部分链表
public ListNode reverse(ListNode head)
{
ListNode pre=null,cur=head;
while(cur!=null)
{
ListNode tmp=cur.next;
cur.next=pre;
pre=cur;
cur=tmp;
}
return pre;
}
}
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
如果要删除头节点,则需要用到哑节点(新建一个伪头节点)。
只有当cur不是重复节点才改变pre,因为我们不希望如cur由3变4时,pre由2变3,而是希望pre保持2。否则只改变pre.next;
public ListNode deleteDuplicates(ListNode head) {
ListNode pHead = new ListNode(0);
pHead.next=head;
ListNode pre=pHead,cur=pHead.next;
while(cur!=null){
if(cur.next!=null&&cur.val==cur.next.val){
while(cur.next!=null&&cur.val==cur.next.val){
cur=cur.next;
}
cur=cur.next;
pre.next=cur;
}
else{
pre=pre.next;
cur=cur.next;}
}
return pHead.next;
}
保留一个重复节点
public ListNode deleteDuplicates(ListNode head) {
ListNode pHead = new ListNode(0);
pHead.next=head;
ListNode pre=pHead,cur=pHead.next;
while(cur!=null){
if(cur.next!=null&&cur.val==cur.next.val){
while(cur.next!=null&&cur.val==cur.next.val){
cur=cur.next;
}
pre.next=cur;//仅在这里有变化
cur=cur.next;
pre=pre.next;
}
else{
pre=pre.next;
cur=cur.next;}
}
return pHead.next;
}
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
方法一:将合并k个链表转化为两两顺序合并。设k个链表中最长长度为n,合并两个链表O2n,i个Oin,n+2n+...kn=O(k^2*n)
方法二:分治合并。递归从下到上 ,第一次合并k/2组链表,每组合并两个链表复杂度2n 总复杂度为nk 第二次合并k/4组链表,每组4个复杂度4n,总复杂度nk,.......第log2k-1次合并两组,每组k/2个。 复杂度为O(nk*logk)
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length==0)return null;
ListNode dum=new ListNode((int)Math.pow(-10,5));
ListNode result=dum;
for(int i=0;i<lists.length;i++)
{
result=mergeTwoLists(result,lists[i]);
}
return dum.next;
}
分治合并
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length==0)return null;
return partitionMerge(lists,0,lists.length-1);
}
public ListNode partitionMerge(ListNode[] lists,int l,int r)
{
if(l==r)return lists[l];
else if(l>r)return null;
else
{
int mid =(l+r)>>>1; //坑 l+(r-l)/2溢出错误
return mergeTwoLists(partitionMerge(lists,l,mid),partitionMerge(lists,mid+1,r));
}
}
public ListNode mergeTwoLists(ListNode head1,ListNode head2)
{
ListNode dum=new ListNode(0);
ListNode l1=head1,l2=head2,cur=dum;
while(l1!=null&&l2!=null)
{
if(l1.val<=l2.val)
{
cur.next=l1;
l1=l1.next;
cur=cur.next;
}
else
{
cur.next=l2;
l2=l2.next;
cur=cur.next;
}
}
if(l1!=null)
{
cur.next=l1;
}
else
cur.next=l2;
return dum.next;
}
}
字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[]
的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
首尾双指针交换内容,逐步向中间移动
public void reverseString(char[] s) {
int head=0,tail=s.length-1;
while(head<tail)
{
char tmp=s[head];
s[head]=s[tail];
s[tail]=tmp;
head++;
tail--;
}
}
27. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并「原地」修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
public int removeElement(int[] nums, int val) {
int fast=0,slow=0;
//fast作用时遍历整个原本的数组,slow表示修改后的数组该位置是什么元素。
//所以若fast不是val就把fast移到slow处,fast是val就跳过这个元素
for(;fast<nums.length;fast++)
{
if(nums[fast]==val)
{
continue;
}
nums[slow++]=nums[fast];
}
return slow;
}