【代码随想录打卡day1---数组】

数组的理论基础

在c++中,数组是连续进行存储的,数组的学习过程中需要注意以下的两点:

  • 数组是进行连续存储的
  • 数组的下标是从0进行开始的

不同的编程语言,二维数组的空间的连续性是不同的,c++的二维数组的空间存储是连续的,而java就不是连续的了。
可以通过以下的c++代码进行测试:

void test_arr() {
    int array[2][3] = {
		{0, 1, 2},
		{3, 4, 5}
    };
    cout << &array[0][0] << " " << &array[0][1] << " " << &array[0][2] << endl;
    cout << &array[1][0] << " " << &array[1][1] << " " << &array[1][2] << endl;
}

int main() {
    test_arr();
}

二分查找

接下来以题目进行分析,下面的题目是来自于leetcode中的第704题。

【题目描述】
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:
1.你可以假设 nums 中的所有元素是不重复的。
2.n 将在 [1, 10000]之间。
3.nums 的每个元素都将在 [-9999, 9999]之间。
思路分析:
解决二分法的问题,最关键的是弄清出边界情况,本题目的边界,无疑就是while(left < right) OR while(left <= right), 以及更新时候的操作。为了解决这些问题,我们需要找到循环不变量,本题目中的循环不变量就是left 和 right,我们在解题的最一开始就定义好边界就不会出现这些问题了。

方案一:
定义边界[left, right], 区间为左闭右闭,我们下面的操作都是基于此。

  • while(left <= right) 要加上=,因为left == right是有意义的,所以要使用left <= right
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

方案二:

定义边界[left, right), 区间为左闭右闭,我们下面的操作都是基于此。
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。

有如下两点:

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

代码:
方案一:

#include
#include
using namespace std;
const int maxn = 1e6 + 10;
vector<int> nums;

int search(vector<int> &nums, int target) {
	int left = 0;
	int right = nums.size() - 1; // 定义左闭右闭[left, right] 
	while (left <= right) { // 当left = right时, [left, right]仍然有效, 所以可以使用left <= right 
		int mid = left + (right - left) / 2; // 防止溢出,等价于left + right / 2 
		if (nums[mid] < target) {
			left = mid + 1;
		}
		else if (nums[mid] > target) {
			right = mid - 1;
		}else {
			return mid;
		}
	}
}
int main() {
	int n, q, target;
	cin >> n;
	cin >> target;
	for (int i = 0; i <= n - 1; i ++) {
		cin >> q;
		nums.push_back(q);
	}
	int result = search(nums, target);
	cout << "最终的目标值为:" << result << endl;
	return 0; 
}

方案二:

#include
#include
using namespace std;
const int maxn = 1e6 + 10;
vector<int> nums;

int search(vector<int> &nums, int target) {
	int left = 0;
	int right = nums.size(); // 定义左闭右闭[left, right] 
	while (left < right) { // 当left = right时, [left, right]仍然有效, 所以可以使用left <= right 
		int mid = left + (right - left) / 2; // 防止溢出,等价于left + right / 2 
		if (nums[mid] < target) {
			left = mid + 1;
		}
		else if (nums[mid] > target) {
			right = mid;
		}else {
			return mid;
		}
	}
}
int main() {
	int n, q, target;
	cin >> n;
	cin >> target;
	for (int i = 0; i <= n - 1; i ++) {
		cin >> q;
		nums.push_back(q);
	}
	int result = search(nums, target);
	cout << "最终的目标值为:" << result << endl;
	return 0; 
}

移除元素

力扣的链接:https://leetcode.cn/problems/remove-element/description/
题目的描述:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。

示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

你不需要考虑数组中超出新长度后面的元素。

思路的分析:
两种思路:暴力求解和双指针算法

方案一:暴力求解
这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。

删除过程如下:(下图引用于代码随想录,网址:https://www.programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE)

暴力求解

暴力求解:
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
#include
#include
using namespace std;
const int maxn = 1e6 + 10;
vector<int> nums;
int n, q, target;
int main() {
	cin >> n;
	cin >> target;
	for (int i = 0; i <= n - 1; i ++) {
		cin >> q;
		nums.push_back(q);
	}
	int size = nums.size();
	for (int i = 0; i <= size - 1; i ++) {
		if (nums[i] == target) { // 发现需要移除的元素,就将数组集体向前移动一位
			for (int j = i + 1; j <= size - 1; j ++) {
				nums[j - 1] = nums[j];
			}
			i --; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
			size --; // 此时数组的大小-1
		}

		
	}
	for (int i = 0; i <= size - 1; i ++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	cout << "更新过后的数组长度为:" << size << endl;
	return 0;
}

方案二:双指针算法
// 时间复杂度:O(n)
// 空间复杂度:O(1)
双指针法(快慢指针法)
没有改变元素的相对位置

#include
#include
using namespace std;
const int maxn = 1e6 + 10;
vector<int> nums;
int n, q, target;
int main() {
	cin >> n;
	cin >> target;
	for (int i = 0; i <= n - 1; i ++) {
		cin >> q;
		nums.push_back(q);
	}
	int size = nums.size();
	for (int fastIndex = 0, lowIndex = 0; fastIndex <= size - 1; fastIndex ++) {
		if (nus[fastIndex] != target) {
			nums[lowIndex ++] = nums[fastIndex];
		}
	}
	for (int i = 0; i <= size - 1; i ++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	cout << "更新过后的数组长度为:" << size << endl;
	return 0;
}

相向的双指针算法
/**

  • 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
    */
// 时间复杂度:O(n)
// 空间复杂度:O(1)
#include
#include
using namespace std;
const int maxn = 1e6 + 10;
vector<int> nums;
int n, q, target;
int main() {
	cin >> n;
	cin >> target;
	for (int i = 0; i <= n - 1; i ++) {
		cin >> q;
		nums.push_back(q);
	}
	int left = 0, right = nums.size() - 1;
	while (left <= right) {
		while(left <= right && nums[left] != target) ++left; // 找左边等于val的元素
		while(left <= right && nums[right] == target) --right; // 找右边不等于val的元素
		if (left < right) nums[left ++] = nums[right --]; // 将右边不等于val的元素覆盖左边等于val的元素
	}
	int size = left; // leftIndex一定指向了最终数组末尾的下一个元素
	for (int i = 0; i <= size - 1; i ++) {
		cout << nums[i] << " ";
	}
	cout << endl;
	cout << "更新过后的数组长度为:" << size << endl;
	return 0;
}

参考:
原网址:代码随想录

你可能感兴趣的:(代码随想录31期打卡,算法,数据结构)