题解
public int[] twoSum(int[] numbers, int target) {
int[] answer = new int[2];
int sum;
//左指针
int start = 0;
//右指针
int end = numbers.length-1;
//终止条件 start==end 此时已经测试完所有可能的结果
while(start<end){
sum=numbers[start]+numbers[end];
if(sum==target){
answer[0]=start+1;
answer[1]=end+1;
break;
}
if(sum<target) start++;
//==和>=都会走该句 但前面==后break 故只有>=会走
else end--;
}
return answer;
}
题解
因为这两个数组已经排好序,我们可以把两个指针分别放在两个数组的末尾,即 nums1 的 m − 1 位和 nums2 的 n − 1 位。每次将较大的那个数字复制到 nums1 的后边,然后向前移动一位。 因为我们也要定位 nums1 的末尾,所以我们还需要第三个指针,以便复制。
public void merge(int[] nums1, int m, int[] nums2, int n) {
//直接利用m和n指向两个数组已有元素的末尾 pos指向nums1末尾
int pos = m-- +n-- -1;
while(m>=0&&n>=0){
//nums2的数较大时移向nums1末尾
if(nums1[m]<=nums2[n]){
nums1[pos--]=nums2[n];
n--;
}else {
nums1[pos--]=nums1[m];
m--;
}
}
//如果 nums1的数字已经复制完,不要忘记把 nums2 的数字继续复制
while(n>=0){
nums1[n]=nums2[n];
n--;
}
}
题解
public boolean judgeSquareSum(int c) {
int middle = (int) Math.sqrt(c);
int left = 0;//左指针
int right = middle;//右指针
long sum;//避免结果溢出
while(right>=left){
sum=left*left+right*right;
if(sum==c) return true;
if(sum>c) right--;
else left++;
}
return false;
}
题解
public boolean validPalindrome(String s) {
//左指针 起始为字符串的开始位置
int left = 0;
int right = s.length() - 1;
char[] chars = s.toCharArray();
int deleteCount = 0;
char first;
char end;
//终止条件 左指针大于右指针
while (left <= right) {
//两指针所对应的字符不等 进行删除
if (chars[left] != chars[right]) {
if (deleteCount == 1) return false;
//删除左指针处字符符合回文的条件
if (chars[left + 1] == chars[right]) {
//验证左指针处字符删除后的下一个字符是否也相等 若相等则可以删除左指针处的字符 否则判断右指针处字符是否可以删除
//TODO 为什么只要下一组也符合就可以选了 因为字符串删错一个后下一个立即反映出来不再是回文字符串 若是字符串删除一个后为回文字符串 则删除错误的字符后 该字符串不再为回文字符串 只有删除正确才是回文
if (left + 2 < right) { //当左指针删除字符的下一个位置在 两个指针的包围区间内时 才要进行判断
if (chars[left + 2] == chars[right - 1]) {
left++;
deleteCount++;
continue;
}
} else {
left++;
deleteCount++;
continue;
}
}
//右指针左移符合的情况
if (chars[left] == chars[right - 1]) {
if (right - 2 >left) {
if (chars[left + 1] == chars[right - 2]) {
right--;
deleteCount++;
continue;
}
} else {
right--;
deleteCount++;
continue;
}
}
//无法通过删除一个得到回文字符串
return false;
}
//满足回文
left++;
right--;
}
return true;
}
//如果s[i]==s[j]继续i++,j--,判断是否回文
//如果s[i]!=s[j]
//1.判断s[i+1]到s[j]范围内字符串是否回文,如果是,去掉s[i]即可
//2.或者判断s[i]到s[j-1]范围内是否回文,如果是,删除s[j]即可,
public boolean validPalindrome(String s) {
char[] ch=s.toCharArray();
for(int i=0,j=ch.length-1;i<j;i++,j--){
if(ch[i]!=ch[j]){
return isPalindrome(ch,i+1,j)||isPalindrome(ch,i,j-1);//不相等直接return 符合只可删一个的条件
}
}
return true;
}
//判断回文
public boolean isPalindrome(char[] s,int i,int j){
while(i<j){
if(s[i]!=s[j]){
return false;
}
i++;j--;
}
return true;
}
题解:
因为题目说字典中最长字符串可以通过删除外字符串中的某些字符得到,
内字符串长度可以转化为外字符串所需删除字符的个数,因此我们只需比较外字符串还原为字典中符合条件的字符串需删除字符的数量即可,所删数量最小的符合内字符串即为所求。这里用数组统计个数。
利用双指针分别指示内字符和外字符串的尾部,逐个比较,不等时,外指针左移表示删除字符,并对对应内字符串删除字符个数加一,相等时,内外指针都左移。重复上述过程直到内外指针之一到尽头。
之后,遍历记录数组,找到对应删除字符个数最少的索引即可。
public String findLongestWord(String s, List<String> dictionary) {
//遍历到内字符串的索引
int index = 0;
//内指针
int inRight;
//外指针
int outRight = s.length() - 1;
char[] temp;
//记录数组
int[] countDeleted = new int[dictionary.size()];
char[] chars = s.toCharArray();
//最小删除字符数
int least = Integer.MAX_VALUE;
//外字符串通过删除最少字符还原为内字符串的索引
int leastIndex = 0;
//对字典元素排序 按字典顺序拿结果
Collections.sort(dictionary);
//外循环终止条件
while (index < dictionary.size()) {
//获取当前内字符串
temp = dictionary.get(index).toCharArray();
//内指针
inRight = temp.length - 1;
//内字符串长度大于外字符串 直接跳过 该字符串不符合要求 不进行逐个字符的比较
if (inRight > outRight) {
outRight = s.length() - 1;
//置记录数组对应位置为最大值
countDeleted[index] = Integer.MAX_VALUE;
//去下个内字符串
index++;
continue;
}
//对内外字符串进行逐个字符的比较
//内循环终止条件
while (inRight >= 0 && outRight >= 0) {
if (chars[outRight] != temp[inRight]) {
//字符不等时 外指针左移
outRight--;
//记录数组对应数值加一
countDeleted[index]++;
} else {
//字符不等时 内外指针左移
outRight--;
inRight--;
}
}
//此时外字符串还有剩余
if (outRight >= 0) countDeleted[index] += outRight + 1;
//内部字符串还有剩余 该字符串不符合要求
if (inRight >= 0) countDeleted[index] = Integer.MAX_VALUE;
outRight = s.length() - 1;
//去下个内字符串
index++;
}
//外字符串删除最少的即为所求
for (int i = 0; i < countDeleted.length; i++) {
if (countDeleted[i] < least) {
least = countDeleted[i];
leastIndex = i;
}
}
//所有内字符串都不符合要求时
if (least == Integer.MAX_VALUE) return "";
return dictionary.get(leastIndex);
}
解法二:
我们也可以封装一个方法来判断内字符串是否为所符合的字符串
然后通过比较不断更新长度最长且字典顺序最小的字符串来获取最优解。
class Solution {
public String findLongestWord(String s, List<String> d) {
String result = "";
for (String t : d) {
if (isSubsequence(t, s)) {
// 获取长度最长且字典顺序最小的字符串
if (result.length() < t.length() || (result.length() == t.length() && result.compareTo(t) > 0)) {
result = t;
}
}
}
return result;
}
// 判断 t 是否为 s 的子序列
public boolean isSubsequence(String t, String s) {
int indext = 0, indexs = 0;
while (indext < t.length() && indexs < s.length()) {
if (t.charAt(indext) == s.charAt(indexs)) {
indext++;
}
indexs++;
}
return indext == t.length();
}
}
题解
原理
设第一次环内相遇时,slow慢指针走了s部,fast快指针走了f部
链表中链表头部到环入口结点(不计环入口节点)有a个节点,环有b个节点
推出 s=nb
从head结点走到入环点需要走 : a + nb, 而slow已经走了nb,那么slow再走a步就是入环点了。
如何知道slow刚好走了a步? 从head开始,和slow指针一起走,相遇时刚好就是a步。
//快慢指针解决
public ListNode detectCycle(ListNode head) {
//慢指针
ListNode slow = head;
//快指针
ListNode fast = head;
do{
//链表中无环的条件
if(fast==null||fast.next==null) return null;
//第一次相遇前快指针走两步,慢指针走一步
fast=fast.next.next;
slow=slow.next;
}while(fast!=slow);
//第一次相遇后 快指针置首部 并改为走一步
fast=head;
//环的起始位置为快慢指针第二次相遇的位置
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return fast;
}
对撞指针是双指针算法之一。
对撞指针从两端向中间迭代数组。一个指针从始端开始,另一个从末端开始。
对撞指针的终止条件是两个指针相遇。
我们假设数组长度为len
:
[0, left)
中不含值为val
的元素;(right, len - 1]
中均为值为val
的元素;left = 0,保证[0,left)初始为空区间;
right = len - 1,保证(right, len - 1]初始为空区间;
当left > right时,上述两个区间完整覆盖数组所有元素,且得到left为去除后数组长度。
public int removeElement(int[] nums, int val) {
//尾指针
int index=nums.length-1;
int temp;
for(int i =0;i<nums.length;i++){
//循环终止条件
if(i>index) break;
//进行交换
if(nums[i]==val){
temp=nums[index];
nums[index]=nums[i];
nums[i]=temp;
index--;
i--;
}
}
//返回个数
return index+1;
}
题解:
利用滑动窗口思想,通过扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串T的所有元素,记录下这个滑动窗口的长度,这些长度中的最小值就是要求的结果。
这个思路其实也不难,**第 2 步相当于在寻找一个「可行解」并记录这个滑动窗口的长度,然后第 3 步在优化这个「可行解」,最终找到局部最优解并记录这个滑动窗口的长度。**左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。最终找到最优解的滑动窗口的长度。
如何判断滑动窗口包含了T的所有元素?
使用数组记录窗口中所需各字符的数量。
一开始滑动窗口为空,用T中各元素来初始化这个need
,表示只需要t中对应的字符
当滑动窗口扩展或者收缩的时候,去维护这个need字典,例如当滑动窗口包含某个元素,我们就让need中这个元素的数量减1,代表所需元素减少了1个;当滑动窗口移除某个元素,就让need中这个元素的数量加1。
如果每次判断滑动窗口是否包含了T的所有元素?结论就是当need
中所有元素的数量都小于等于0时,表示当前滑动窗口不再需要任何元素。
c
,不仅need[c]的数量减少1,同时count也要减少1,这样我们通过count就可以知道是否满足条件,而无需遍历字典了。need
记录了遍历到的所有元素,而只有need[c]>0
大于0时,代表c
就是所需元素 public String minWindow(String s, String t) {
int[] need = new int[128];
//记录字符串t中每个字符的数量
for (char ch : t.toCharArray())
need[ch]++;
//字符串t的数量
int count = t.length();
int left = 0;//窗口的左边界
int right = 0;//窗口的右边界
//覆盖t的最小长度
int windowLength = Integer.MAX_VALUE;
//覆盖字符串t开始的位置
int strStart = 0;
while (right < s.length()) {
if (need[s.charAt(right++)]-- > 0)
count--;
//如果全部覆盖
while (count == 0) {
//如果有更小的窗口就记录更小的窗口
if (right - left < windowLength) {
windowLength = right - left;
strStart = left;
}
if (need[s.charAt(left++)]++ == 0)
count++;
}
}
//如果找到合适的窗口就截取,否则就返回空
if (windowLength != Integer.MAX_VALUE)
return s.substring(strStart, strStart + windowLength);
return "";
}