1.基础知识
1.1数组是存放在连续内存空间上的相同类型数据的集合。
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的。
- 数组的元素是不能删的,只能覆盖。
- 二维数组在内存的空间地址在C++中是连续分布的,在java中是不连续的。(Java是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机,所以看不到每个元素的地址空间情况,可以找出经过处理后的数值。)
2.相关知识
3.力扣题目
3.1 704 二分查找
3.1.1 自己做法
//第一反应和二分查找没有一毛钱关系的,还在想这题咋这么简单(微笑)
class Solution {
public int search(int[] nums, int target) {
for(int i = 0; i < nums.length; i++){
if(target == nums[i]){
return i;
}
}
return -1;
}
}
//二分查找
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = left + (right - left)/2;
if(target == nums[mid]){
return mid;
}else if(target > nums[mid]){
left = mid + 1;
}else if(target < nums[mid]){
right = mid - 1;
}
}
return -1;
}
}
出现的问题(快反思)
- 求数组的长度用的是:nums.length 你用的是:nums.size()
- 第一反应写的和二分查找没有一毛钱关系
- 判断用的是==而不是=
- 二分查找结束条件:left>right 而不是==
- .int mid = left+(right - left)/2; -----防止整数溢出(怕right + left结果超过int范围)(而且这个语句要放在循环里,因为每次mid值都要改)
- 更新left和right的值:
left = mid + 1;
right = mid - 1;
不能直接left/right = mid;
(这样设置的话万一target不在数组中(比如比数组中的最大值还大),/法两个相邻的值求一直等于较小的值,就死循环了。比如在[1,2,3]里找4)
left=0-1-1-1-1
right=2-2-2-2-2
(就一直1、2循环下去了,left不会大于right)
正确的
left=0-2-3
right=2-2-2
(此时就不满足条件退出循环)
(别纠结:反正target != mid了,缩小后肯定也不要它了)
3.1.2 参考做法
- 写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)
- 两种判断while里条件是<=还是<:可以想象left=right,此时若right还代表最后一个数,此时就是右闭了。所以右闭是<=,右开是<。([1,1]就代表1,就是两边闭可以相等。[1,1) 这明显就是错误的式子,既包括1又不包括1,所以一开一闭不能相等。 )
- 判断right是mid还是mid-1:右闭是包含right的,而此时mid已经不是了,就要把mid从下轮要找的区间里排除,所以右闭就是mid-1,右开是mid。
- left因为两种都是闭,所以都是mid+1.
- right的初始值,右闭的话包含right所以取数组的最大下标就行,右开的话因为不包含right,所以right的取值就要比数组实际大1,才能把数组整个表示到。
左闭右闭即[left, right]
//自己写的那个应该就属于这个
right = nums.length - 1
while(left <= right)
right = mid - 1
class Solution {
public int search(int[] nums, int target) {
// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] < target) {
left = mid + 1;
}
else { // nums[mid] > target
right = mid - 1;
}
}
// 未找到目标值
return -1;
}
}
左闭右开即[left, right)
right = nums.length
while(left < right)
right = mid
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] < target) {
left = mid + 1;
}
else { // nums[mid] > target
right = mid;
}
}
// 未找到目标值
return -1;
}
}
3.2 27 移除元素
3.2.1 自己做法
class Solution {
public int removeElement(int[] nums, int val) {
int count = nums.length;
for(int i = 0; i <= count-1; i++){
if(nums[i] == val){
for(int j = i; j < count - 1; j++){
nums[j] = nums[j+1];
}
i--;
count--; //这里没有想到可以直接总数-1.
}
}
return count;
}
}
出现的问题(快反思)
- 这属于暴力解法。
3.2.2 参考做法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新 新数组下标的位置
fast指向的若不等于val的:fast值赋给slow的,即遇到相等的就走,遇到不相等的就给slow传回去。(一开始俩一起走的时候也是赋值了,只是俩指向同一个,所以相当于没赋值。)
slow遇到不相等的则+1,遇到相等的就不变,等着fast遇到不相等的给他传回来。
(把需要的数组想象成新建了一个新数组:slow相当于一个新数组,只存!=val的值,fast相当于在与数组中找!=val的值)
(所以slow只要遇到=val的值就停下,等着fast给他找到!=val的值把这个一替换)
(slow最后的值就是新数组中的大小)
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0;
for(int fast = 0; fast <= nums.length - 1; fast++){
if(val != nums[fast]){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
}
4.总结
数据结构差不多忘完了,第一次做算法题,做的真shi啊(暴风哭泣)
二分法查找:有序、无重复元素
1. mid值的计算,left+(right - left)/2防止溢出。
2. while循环满足的条件。
3. left和right的移动。