https://leetcode-cn.com/problems/sort-array-by-parity/
使用两个指针,第一个指针从前往后寻找奇数,第二个指针从后往前寻找偶数,找到了就交换,然后分别继续往前往后寻找,直到两个指针相遇
class Solution {
public int[] sortArrayByParity(int[] nums) {
int n=nums.length;
int i=0,j=n-1;
while(i<j)
{
while(i<n&&nums[i]%2==0)//找到前面的奇数
i++;
while(j>=0&&nums[j]%2==1)//找到后面的偶数
j--;
if(i>=j)//寻找完毕
break;
int tmp=nums[i];//交换
nums[i]=nums[j];
nums[j]=tmp;
i++;
j--;
}
return nums;
}
}
//O(n)
//O(1)
https://leetcode-cn.com/problems/longest-palindromic-substring/
思路:中心拓展法 枚举中心点,然后向两侧拓展 如果两侧指针指向的字符不相等 则停止拓展 中心点可能是1个,比如aba这种以b作为中心点,也可能是2个,比如abba这种以bb为中心点,因此一共有n个单个字符中心点,n-1个双字符中心点(一共2n-1种情况)不需要3字符和4字符中心点,因为它们分别可以从单字符和双字符拓展得到
class Solution {
public String longestPalindrome(String s) {
int maxLen=0,n=s.length();
int start=0;
for(int i=0;i<2*n-1;i++)
{
//i为偶数时 left=right 单中心点
//i为奇数时 right=left+1 双中心点
int left=i/2,right=left+i%2;
while(left>=0&&right<n&&s.charAt(left)==s.charAt(right))
{
int len=right-left+1;
if(len>maxLen)//更新最大长度
{
maxLen=len;
start=left;//更新起点
}
left--;
right++;
}
}
return s.substring(start,start+maxLen);//起点+最大长度确定最长回文子串
}
}
//O(n)
//O(1)
https://leetcode-cn.com/problems/container-with-most-water/
思路:假设左边的线的高度为h1,右边的线的高度为h2, 那么由这两条线所形成的容器可以容纳的水的体积为:min(h1,h2)*(j-i)
即水的体积由短板决定 那么如果继续移动长板,假设h1>h2, h1增加的话,水的体积不变; h1减小的话,水的体积可能减小也可能不变; 移动h2的话,h2增加水的体积增加;h2减小的话,水的体积减小; 所以要想水的体积增大,只有一种可能,就是移动h2(短板)
所以每次移动短板获得新的水的体积,不断更新最大值即可(贪唯一的增加可能性)
class Solution {
public int maxArea(int[] height) {
int left=0,right=height.length-1;
int ans=0;
while(left<right)
{
if(height[left]>height[right])
{
ans=Math.max(ans,(right-left)*height[right]);
right--;
}
else
{
ans=Math.max(ans,(right-left)*height[left]);
left++;
}
}
return ans;
}
}
//O(n)
//O(1)
https://leetcode-cn.com/problems/3sum/
思路:先对数组按升序排序,排序的好处: 记第一个元素为nums[first], 当nums[first]>0时,则说明后面的元素都大于0,不可能形成a+b+c=0的情况,此时结束循环;相等的元素一定连续在一起,这样利于去重。 具体思路:先固定第一个元素的下标first, first的范围为[0,n-2], 然后需要寻找的目标就是-nums[first], 照nums[second]+nums[third]=target时,可以采用双指针法,left从前往后遍历,right从后往前遍历, 二者之和分为以下3种情况:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans=new ArrayList<>();
if(nums.length<3)
return ans;
int n=nums.length;
Arrays.sort(nums);
for(int first=0;first<n-2;first++)
{
if(nums[first]>0)//后面的元素都大于0了
break;
if(first>=1&&nums[first]==nums[first-1])//-1 -1 -1 ....只需要以第一个-1开头就行了
continue;
int target=-nums[first];
int left=first+1,right=n-1;
while(left<right)
{
if(nums[left]+nums[right]==target)
{
List<Integer> tmp=new ArrayList<>();
tmp.add(nums[first]);
tmp.add(nums[left]);
tmp.add(nums[right]);
ans.add(tmp);
left++;
right--;
while(left<right&&nums[left]==nums[left-1])//-2 0 0 2 2 排除这种重复的情况
left++;
while(left<right&&nums[right]==nums[right+1])//-2 0 0 2 2 排除这种重复的情况
right--;
//-2 0 0 2 2只需要一个-2 0 2 当nums[first]=-2 nums[left]=nums[1]=0 nums[right]=nums[4]=2
//此时满足条件 但是需要排除后面和nums[left]以及nums[right]相等的连续元素
}
else if(nums[left]+nums[right]<target)
left++;
else if(nums[left]+nums[right]>target)
right--;
}
}
return ans;
}
}
//O(n^2)
//O(logn) 排序的空间复杂度
https://leetcode-cn.com/problems/next-permutation/
思路: 想要获取下一个排列,要进行以下3步(以2 3 1 为例)
ps: 特殊情况,当例子为3 2 1时,会发现第1步中找到的i=-1, 因此直接进行第4步,翻转[0,2]区间,即翻转所有元素,这与直接的观察结果也符合
class Solution {
public void nextPermutation(int[] nums) {
int n=nums.length;
int i=n-2;
while(i>=0&&nums[i]>=nums[i+1])//不能改成nums[i]>nums[i+1]
i--;
if(i>=0)
{
int j=0;
for(j=n-1;j>=i+1;j--)
{
if(nums[j]>nums[i])
break;
}
swap(nums,i,j);
}
reverse(nums,i+1,n-1);
}
public void swap(int[] nums,int i,int j)
{
int tmp=nums[i];
nums[i]=nums[j];
nums[j]=tmp;
}
public void reverse(int[] nums,int i,int j)
{
while(i<j)
{
swap(nums,i,j);
i++;
j--;
}
}
}
//O(n)
//O(1)
https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
思路:先使用两个指针pA pB分别指向headA headB 当pA!=pB时 如果A链表没有遍历完,pA往后走一步,如果A遍历完了,就让pA指向headB, 从B链表的头部开始移动; 对于headB同理
https://leetcode-cn.com/problems/is-subsequence/
思路:这里要求的是子序列,不是连续子序列,设置两个指针i,j分别扫描s和t, 具体做法为,当s[i]==t[j]
时,i++, 而j一直往后走,如果最后i==s.length()
则匹配成功
class Solution {
public boolean isSubsequence(String s, String t) {
int i=0,j=0;
int m=s.length(),n=t.length();
while(i<m&&j<n)
{
if(s.charAt(i)==t.charAt(j))
i++;
j++;
}
return i==m;
}
}
//O(m+n)
//O(1)
进阶问题: 预处理出对于 t的每一个位置,从该位置开始往后每一个字符第一次出现的位置
构造一个dp数组dp[i][j]
: 表示从t中的位置i开始字符第一次出现的位置
d p [ i ] [ j ] = { i , if t [ i ] = = j d p [ i + 1 ] [ j ] , if t [ i ] ! = j dp[i][j] = \begin{cases} i, & \text{if $t[i]==j$} \\ dp[i+1][j], & \text{if $t[i]!=j$ } \\ \end{cases} dp[i][j]={i,dp[i+1][j],if t[i]==jif t[i]!=j
如果在t中,i位置上面的字符就是j, 即t[i]==j
则dp[i][j]=j
如果不是, j 出现在位置 i+1 开始往后,即dp[i+1][j]
class Solution {
public boolean isSubsequence(String s, String t) {
int m=s.length(),n=t.length();
int [][] dp=new int[n+1][26];//只有26个小写字母
for(int i=0;i<26;i++)
dp[n][i]=n;//t中的位置是[0,n-1] n位置额外腾出来表示不存在
//dp[i][j]=dp[i+1][j] 这样当t[n-1]!=j 时 dp[n-1][j]=dp[n][j]=n 表示n-1位置往后不存在字符j
for(int i=n-1;i>=0;i--)//从后到前处理dp数组 因为当前位置的情况依赖于后面的
{
for(int c=0;c<26;c++)
{
if(t.charAt(i)==c+'a')
dp[i][c]=i;
else
dp[i][c]=dp[i+1][c];
}
}
int index=0;
//s串是否是t串的子序列
//m=s.length() n=t.length()
for(int i=0;i<m;i++)
{
int c=s.charAt(i)-'a';
if(dp[index][c]==n)
return false;//s中的字符c在t中不会出现 则可以判断不是子序列
index=dp[index][c]+1;//c在t中出现的位置是dp[index][c] 所以s中的下一个字符和dp[index][c]比较
}
return true;
}
}
//O(n*26+m)
//O(26*n)
https://leetcode-cn.com/problems/remove-element/
思路:题目要求原地修改数值,最后返回的是数组的长度,可以使用两个指针,一个指针先固定在某个元素x,另外一个指针从元素x的下一个元素开始往后遍历,直到遇到一个元素y,y!=x, 然后将y放在x的下一个位置即可,重复这个过程,直到right指针遍历完数组
class Solution {
public int removeDuplicates(int[] nums) {
int left=0,right=1,n=nums.length;
if(n==0)
return 0;
while(right<n){
if(nums[right]!=nums[left])
{
nums[left+1]=nums[right];
left++;
}
right++;
}
return left+1;
}
}
//O(n)
//O(1)
思路:所以双指针,一个left指针指向当前构造出的新数组的最后一个元素,一个right指针往后寻找和val不等的元素,找到后就将该元素放置在
left` 的位置上面
class Solution {
public int removeElement(int[] nums, int val) {
int left=0,right=0,n=nums.length;
while(right<n){
//right往后寻找和val不等的元素
if(nums[right]!=val){
nums[left]=nums[right];
left++;
}
right++;
}
return left;//返回新数组的长度
}
}
//o(n)
//O(1)
https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
思路:先使用一个指针left指向当前头节点,然后另外一个指针right往后寻找和left节点的值不一样的节点,找到后将right指向的节点连接在left节点后面,然后使left指向right, 继续上述过程; 最后left指向尾结点,需要断开left和后面重复节点的连接
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null)
return null;
ListNode left=head,right=head;
while(right!=null){
if(right.val!=left.val){//right指向的节点和left指向的节点值不同
left.next=right;//right节点连接到left上
left=right;//left更新为right
}
right=right.next;
}
left.next=null;//断开后面重复的连接
return head;
}
}
//O(n)
//O(1)
https://leetcode-cn.com/problems/move-zeroes/
思路:使用right和left两个指针,left指针指向已处理的左边的最后一个非0元素,right则往后寻找非0元素,当right找到了非0元素,分为2种情况:
nums[left]=nums[right] nums[right]=0
class Solution {
public void moveZeroes(int[] nums) {
int n=nums.length;
int left=0,right=0;
while(right<n){
if(nums[right]!=0)
{
if(right!=left){
nums[left]=nums[right];//把非0元素放到left位置上
nums[right]=0;//right位置上面放0
}
left++;//left=right&&nums[right]!=0 不用交换
}
right++;
}
}
}
//O(n)
//O(1)
https://leetcode-cn.com/problems/interval-list-intersections/
思路:对于两个区间,先获得两个区间左边界中的较大值,右边界中的较小值,如果这个较大值<=较小值,说明有交集,将该交集加入答案中,另外比较完当前区间后,需要移动右边界较小的区间,因为该区间不会和自己的区间列表内的元素有交集,也不会和另外一个区间列表的下一个区间有交集
class Solution {
public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {
int n1=firstList.length,n2=secondList.length;
ArrayList<int[]> ans=new ArrayList<>();
if(n1==0||n2==0)
return ans.toArray(new int[ans.size()][]);
int i=0,j=0;
while(i<n1&&j<n2){
int start=Math.max(firstList[i][0],secondList[j][0]);
int end=Math.min(firstList[i][1],secondList[j][1]);
if(start<=end){
ans.add(new int[]{start,end});
}
if(firstList[i][1]<secondList[j][1])//移动结束较早的区间
i++;
else
j++;
}
return ans.toArray(new int[ans.size()][]);
}
}
//O(m+n)
//O(m+n)
https://leetcode-cn.com/problems/advantage-shuffle/
思路:类似于田忌赛马,将A中元素升序,B中元素降序,当A中的最大元素可以打败B中的最大元素时,则二者匹配;否则,用A中的最小元素来和B中的最大元素匹对
class Solution {
public int[] advantageCount(int[] nums1, int[] nums2) {
PriorityQueue<int[]> pq=new PriorityQueue<>((a,b)->b[1]-a[1]);//降序 堆顶元素最大
for(int i=0;i<nums2.length;i++){
pq.offer(new int[]{i,nums2[i]});
}
Arrays.sort(nums1);//升序
int[] ans=new int[nums1.length];
int left=0,right=nums1.length-1;//left指向最小元素 right指向最大元素
while(!pq.isEmpty()){
int[] tmp=pq.poll();
int index=tmp[0],maxVal=tmp[1];
if(nums1[right]>maxVal){//A中最大>B中最大
ans[index]=nums1[right];
right--;
}else{
ans[index]=nums1[left];//A中目前最小-B中目前最大
left++;
}
}
return ans;
}
}
//O(nlogn)
//O(n)
https://leetcode-cn.com/problems/4sum/
思路:类似于15. 三数之和,先对数组进行排序,方便去重,一段连续相同的数字只取第一个,然后先固定住第一个数字和第二个数字,对于第3个数字和第4个数字可以使用双指针的方法求解,在求解第3个数字和第4个数字的时候也需要去重
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> ans=new ArrayList<>();
int n=nums.length;
for(int a=0;a<n-3;a++){
while(a>0&&a<n-3&&nums[a-1]==nums[a])//去重
a++;
for(int b=a+1;b<n-2;b++){
while(b>a+1&&b<n-2&&nums[b-1]==nums[b])//去重
b++;
int left=b+1,right=n-1;
int val=target-nums[a]-nums[b];
while(left<right){
if(nums[left]+nums[right]==val){//找到1个答案
List<Integer> tmp=new ArrayList<>();
tmp.add(nums[a]);
tmp.add(nums[b]);
tmp.add(nums[left]);
tmp.add(nums[right]);
ans.add(tmp);
left++;
right--;
while(left<right&&nums[left]==nums[left-1])//去重
left++;
while(left<right&&nums[right]==nums[right+1])//去重
right--;
}
else if(nums[left]+nums[right]<val)//和偏小 left++
left++;
else if(nums[left]+nums[right]>val)//和偏大 right--
right--;
}
}
}
return ans;
}
}
//O(n^3)
//O(nlogn)
https://leetcode-cn.com/problems/shortest-distance-to-a-character/
思路:两遍扫描,第一遍求出每个字符距离左边最近的c的距离,第二次扫描遍历每个字符距离右边最近的c的距离,取二者中的较小值即可
class Solution {
public int[] shortestToChar(String s, char c) {
int n=s.length();
int[] ans=new int[n];
Arrays.fill(ans,n+2);
int j=-1;
for(int i=0;i<n;i++){
if(s.charAt(i)==c)
j=i;
if(j!=-1)
ans[i]=Math.min(ans[i],i-j);
}
j=-1;
for(int i=n-1;i>=0;i--){
if(s.charAt(i)==c)
j=i;
if(j!=-1)
ans[i]=Math.min(ans[i],j-i);
}
return ans;
}
}
//O(n)
//O(1)