一.二分查找:
①二分查找应用于有序数组的寻找位置问题。
②根据需要寻找的值和区间中值的关系更新左右边界值。
③在中值右边,更新左边界;在中值左边,更新右边界。
④要注意:确定好是左闭右闭还是左闭右开。
代码:
//二分查找要根据区间来判断的,通过目标值和区间中间值的大小关系,来变换区间边界
class Solution {
public int search(int[] nums, int target) {
if(target < nums[0] || target > nums[nums.length-1]){
return -1;
}
return helper(nums, target);
}
private int helper(int[] nums, int target){
int left = 0;
int right = nums.length-1;
while(left <= right){
int mid = (left+right)/2;
if(target > nums[mid]){
left = mid+1;
}else if(target < nums[mid]){
right = mid-1;
}else{
return mid;
}
}
return -1;
}
}
二.移除元素:
①暴力的解法:也是数组的移除的基本思想---遇到要移除的元素了,将其后面所有的元素都向前移一位,然后总长度-1.
②Note:要注意的就是,因为此时移动的时候,原来i+1的位置的值现在移动到了i位置,所以要判断原i+1位置是否是要删除的元素,我们还要i--。
代码:
//暴力解法,遍历数组,遇到相等的,就把后面的都向前移一位。
//要注意的就是移动完之后,i+1位置的值,变成了i位置的值,而原来i+1位置的值还未与val比较即现在i位置的值未与val比较所以要i--
//还有一点要注意,因为我们的size删完一个元素要减减,所以在写循环的时候的右边界是size而不一直是length
class Solution {
public int removeElement(int[] nums, int val) {
if(nums.length == 0){
return 0;
}
int size = nums.length;
for(int i = 0; i < size; i++){
if(nums[i] == val){
for(int j = i+1; j < size; j++){
nums[j-1] = nums[j];
}
i--;
size--;
}
}
return size;
}
}
③双指针解法:快慢指针初始均指在下标为0的位置,快指针负责遍历数组;慢指针负责收集最后的结果,因为我们不用考虑新数组其余位置的值,即返回新数组的长度和对应的值即可。
④那么怎么模拟移除元素呢?就是当遇到要移除的元素,我们直接不处理,即直接跳过,就想当于移除了,而遇到的元素不是要删除的元素的时候,直接收集,并且慢指针++。
⑤慢指针的值表示的就是下一个元素的位置,当然到最后的时候,它表示的就是新数组的长度。
代码:
//双指针做法,即想要删除元素,那么碰到要删的元素的时候就直接跳过,用慢指针记录新的数组,用快指针遍历数组,最后因为要求的是数组的长度,直接返回慢指针即可。
class Solution {
public int removeElement(int[] nums, int val) {
if(nums.length == 0){
return 0;
}
int slowIndex = 0;
for(int fastIndex = 0; fastIndex < nums.length;fastIndex++){
if(nums[fastIndex] != val){
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
}
三.删除有序数组中的重复项
题目描述:
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件
解法:
①首先我们看到删除数组,可以试试双指针写法。
②确定快慢指针指向哪?因为本题求没有重复的数组,那么第一个元素肯定不重复,所以快慢指针都指向下标为1的位置。
③重复的话要删掉,所以依旧快指针遍历,慢指针收结果,即当前的元素如果和前面的元素重复了的话,就跳过,否则的话就收集,收集的同时要注意慢指针++。
④慢指针仍旧表示的是最后的新数组的长度。
代码:
//慢指针的数组中最开始就有一个元素,因为一个元素不可能重复。
//要用快指针去比较当前的和前一个是否相等,所以要从1到nums.length-1
//然后当不等的时候取当前元素
//最后返回满指针就是数组的长度
class Solution {
public int removeDuplicates(int[] nums) {
int slowIndex = 1;//刚开始有一个元素,所以下一个收集位置是1.
// nums[slowIndex] = nums[0];
// slowIndex++;
for(int fastIndex = 1; fastIndex < nums.length; fastIndex++){
if(nums[fastIndex] != nums[fastIndex-1]){
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
}
四. 移动零
题目描述:
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
解法:
①此题其实和移除元素很类似。即遇到0的跳过,那么要解决的问题就是收集非是0的元素后,剩余的0应该从哪开始填充呢?
②在移除元素的那题中,slowIndex既表示下一个要收集的位置也表示新数组的长度。
所以就从slowIndex到元素末尾都赋0即可。
代码:
//碰到不是0的收集,到最后,slowIndex的位置就是第一个0的位置,到末尾都置0即可
class Solution {
public void moveZeroes(int[] nums) {
int slowIndex = 0;
for(int fastIndex = 0; fastIndex < nums.length; fastIndex++){
if(nums[fastIndex] != 0){
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
for(int j = slowIndex; j < nums.length; j++){
nums[j] = 0;
}
}
}
五. 比较含退格的字符串
题目描述:
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
解法:
首先要理解题意,即#会移除掉其前面一个位置的元素,同时#位置处的元素也被移除掉了。
此题其实也相当于删掉符合题目要求的元素得到新数组,故考虑双指针做法:
①双指针的指向分别为s和t的末尾,因为#会移除其前面的元素,所以我们要从后向前遍历,所以指向s和t的末尾。
②我们考虑什么时候是表示两个字符串相等,即他俩同时遍历完且遍历的过程中不出现false的情况,故大循环条件 --- while(i >= 0 || j >= 0)即只有同时遍历完之后才跳出,然后返回true。
③对于每一个小的while循环,遇到# 计数器++,i--;没遇到#,计数器>0,计数器--,i--;没遇到#,且计数器 ==0 ,跳出小循环比较。
④比较:i>=0 && j>=0,对应的值不等,false。
否则的话,如果 i>=0 || j >=0 ,false。--- 即一个有当前位置的元素,一个已经遍历完了且没有当前元素,故一定是一个字符串比另外一个字符串至少多一个字符。
剩下的情况就是都大于0,值相等,此时是匹配上了,i--,j--。
还有就是都小于0,那么此时再--更小于0,即跳出大循环,结束比较。
⑤经历过比较后,没有出现错误,就返回true。
代码:
//此题不论怎么做都应该想到,#是删前面的值,所以我们要从后向前遍历。
//用双指针来做,一个指向S的末尾,一个指向T的末尾
//首先要明确,当两个指针同时为负数并且中途比较没有错误,最后返回true;故大的while的条件就是i>=0||j>=0
//对于每一个指针,遇到了#,计数器++,指针前移;没遇到#,计数器>0,计数器--,指针前移;没遇到#,计数器=0,直接跳出循环进行比较。
//比较:如果ij同时>=0,且数值不等,就false
//否则的话,只要有1方大于0,就是false。
//剩下的情况就是都>=0,且相等;和同时小于0
//都大于0且相等,说明匹配上了,ij同时前移---i--,j--;同时小于0,说明比较完毕,下面又--了更小于0了,就直接跳出循环。
//循环外表示的就是,比较完两个字符串不出现false的情况,所以return true。
//3 while 1 if
class Solution {
public boolean backspaceCompare(String S, String T) {
char[] ch1 = S.toCharArray();
char[] ch2 = T.toCharArray();
int i = ch1.length - 1;
int j = ch2.length - 1;
int count1 = 0;
int count2 = 0;
while(i >= 0 || j >=0){
while(i >= 0){
if(ch1[i] == '#'){
i--;
count1++;
}else if(count1 > 0){
count1--;
i--;
}else{
break;
}
}
while(j >= 0){
if(ch2[j] == '#'){
j--;
count2++;
}else if(count2 > 0){
j--;
count2--;
}else{
break;
}
}
if(i >= 0 && j >= 0){
if(ch1[i] != ch2[j]){
return false;
}
}else{
if(i >=0 || j >= 0){
return false;
}
}
i--;
j--;
}
return true;
}
}
⑥用StringBuilder来做:即写一个函数用于获得满足条件后的新字符串:
⑦用while循环,遇到# 计数器++,i--;没遇到#,计数器>0,计数器--,i--;没遇到#,且计数器 ==0 ,说明匹配上了,就append该字符,并且i--。
⑧最后调用主函数进行比较得到结果即可。
代码:
//用StringBuilder来做:统计#出现的次数,如果当前元素是#,i--,count++;如果当前元素不是#,且count>0,i--,count++;否则的话;append元素,i--;最后调用主函数比较返回结果。
class Solution {
public boolean backspaceCompare(String S, String T) {
return convert(S).equals(convert(T));
}
private String convert(String s){
StringBuilder sb = new StringBuilder();
int i = s.length()-1;
int count = 0;
while(i >= 0){
if(s.charAt(i) == '#'){
count++;
i--;
}else if(count > 0){
i--;
count--;
}else{
sb.append(s.charAt(i));
i--;
}
}
return sb.toString();
}
}
两种方法比较:
①双指针的时间复杂度是O(m+n),空间复杂度是O(1)
②用StringBuilder的时间复杂度是O(m+n),空间复杂度也是O(m+n)---因为要动态开辟两个空间用于存储新得到的字符串。但是StringBuider的方法代码更简单,也更容易理解些。
六.总结:
①复习了两道旧题,对于二分法和移除元素有了更深的理解---分别为对应区间问题和双指针问题。
②剩下的三道题都是双指针问题,最后形成的思路就是遇到移除数组中元素问题,可以考虑双指针的思想。