无意间看到训练营的活动,感觉群里高手云集,我这个小趴菜瑟瑟发抖。对于刷题我总是没有自主性,每次遇到问题卡了一次就停下好几天。加入训练营不求别的,希望自己能坚持下去,并做好记录!第一天的内容不多,是自己之前学过的,但是再做还是卡了很久。
704. 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。(数组有序且无重复元素)
示例:
输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在nums中并且下标为 4
这道题以前数据结构课上学过,不过并没有亲身实践,自己做了一遍后也是对该算法的原理有了更深刻的理解。
1.暴力算法
public int search(int[] nums, int target) {
for(int i=0;i<nums.length;i++){
if(nums[i]==target){
return i;
}
}
return -1;
}
遍历每个数组元素依次与目标值进行判断,若相等则返回下标值即可。
2.二分查找
二分查找的前提:数组有序且无重复,本题为二分查找法的经典入门应用。
2.1 左闭右闭区间
int left=0;
int right=nums.length-1;
while (left<=right){
if(left==right&&nums[left]==target){
return left;
}
int mid=(left+right)/2;
if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]>target){
right=mid-1;
}else {
return mid;
}
}
return -1;
对于左右都是闭区间的情况,首先left和right值可以相等取同一个值。
当nums[mid]值>target时,则表明mid代表的元素过大,应减小right指针的值。且由于mid!=target && right为闭区间,因此设置right值为mid-1。
反之,当nums[mid]值<target,设置left=mid+1;
重复上述操作,直至nums[mid]==target,返回mid值。
2.2 左闭右开区间
public static int search(int[] nums, int target) {
int left=0;
int right=nums.length;
while(left<right){
int mid=(left+right)/2;
if(nums[mid]<target){
left=mid+1;
}
else if(nums[mid]>target){
right=mid;
}else{
return mid;
}
}
return -1;
}
对于右开区间,因为取不到下标为right的值,因此right初值应为nums.length(相当于下标为length-1)。
并且由于是左开右闭区间,因此left与right值无法相等。例如若left=right=1,[1,1)不是一个正确的区间。
当nums[mid]值>target时,由于mid!=target && right为开区间,因此设置right值为mid(相当于下标为mid-1)。
27.移除元素
给你一个数组
nums
和一个值val
,你需要 原地 移除所有数值等于val
的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用
O(1)
额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例: 输入:nums = [3,2,2,3], val = 3 输出:2, nums = [2,2]
1.暴力解法
这道题我本来想的是记录相等元素的个数然后返回length-count值,发现实现起来十分麻烦(也可能是我没写出最优解)。后来看了题解发现直接定义size变量,当出现时直接size--,返回size即可。
public static int removeElement(int[] nums, int val) {
int len = nums.length;
if (len == 1 && nums[0] == val) {
return 0;
}
if (len == 1 && nums[0] != val) {
return 1;
}
int count = 0;
int j = len - 1;
for (int i = 0; i < len ; i++) {
if(i>j){break;}
if(i==j){
if(nums[i]==val){
count++;
break;
}
}
if (nums[i] == val) {
count++;
while (nums[j] == val&&i!=j) {
j--;
count++;
}
if(count==len){return 0;}
int t;
t = nums[i];
nums[i] = nums[j];
nums[j] = t;
j--;
}
}
return len - count;
}
双层for循环,第一层循环遍历数组,若该下标对应元素==target,则在第二层for循环中对该元素进行覆盖(nums[j]=nums[j+1])。
public static int removeElement(int[] nums, int val) {
int size = nums.length;
for (int i = 0; i < size; i++) {
if (nums[i] == val) {
for (int j = i; j < nums.length-1; j++) {
nums[j] = nums[j+1];
}
i--; //重要!当前i被覆盖为nums[i+1]的值,
//但下次循环后i++相当于对nums[i+2]进行判断,并没有判断nums[i+1]的值。
size--;
}
}
return size;
}
2.快慢指针
这个一开始并没有想到。感觉很多题都可以用双指针解决,自己还需多多磨练。
fast指针遍历整个数组,slow指针代表当前新数组(未包含目标元素)的下标。当nums[fast]!=val时,快慢指针同时加一,当nums[fast]==val,则fast+1,slow不动。最后返回slow指针即为新数组的元素个数。
public static int removeElement(int[] nums, int val) {
int left=0;
int right=0;
for(;right<nums.length;right++){
if(nums[right]!=val){
nums[left]=nums[right];
left++;
}
}
return left;
}