数组和字符串
集合、列表和数组
- 集合:集合里的元素类型不一定相同;集合里的元素没有顺序
- 列表(线性列表):具有顺序,且长度可变
- 数组:列表的实现方式之一。在Java中元素类型必须一致,有索引,从0开始,连续存储
数组的操作
-
读取元素:从 0 开始。对于数组,计算机会在内存中申请一段连续的空间,记下索引为 0 处的内存地址。
比如 C O D E R ,想找 D 的时候,就从 C 的内存地址开始,加上索引值。因此时间复杂度是常数级别,O(1)。
-
查找元素:查找元素时,从头开始向后查找,没有就报异常。最坏的情况为 n 次,n为数组的长度,所以为O(n)。
-
插入元素:末尾插入时,仅需一步;其他位置插入时,需要将插入位置以后的元素向后挪,再插入。
-
删除元素:末尾删除时,仅需一步;其他位置删除时,需要先删除,在挪位置,O(n)。
寻找数组的中心索引
中心索引:数组中心索引的的左侧的所有元素相加的和等于右侧所有元素相加的和。不存在时返回-1.如果有多个,返回最左边的。
输入:
nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
索引 3 (nums[3] = 6) 的左侧数之和 (1 + 7 + 3 = 11),与右侧数之和 (5 + 6 = 11) 相等。
同时, 3 也是第一个符合要求的中心索引。
输入:
nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心索引。
方法
前缀和
S是数组的和,当索引 i 是中心索引时,位于 i 左边数组元素的和 leftsum 满足 S-nums[i]-leftsum.
只需要判断当前索引 i 是否满足 leftsum == S-nums[i]-leftsum,并计算 leftsum的值
class Solution0804{
public int pivotIndex(int[] nums) {
int sum = 0;
int leftSum = 0;
for (int num : nums) {
sum += num;
}
for (int i = 0; i < nums.length; i++) {
if (leftSum == sum-nums[i]-leftSum ){
return i;
}
leftSum += nums[i];// 注意位置,不能少,也不能放在if上面,否则会减两遍
}
return -1;
}
}
搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
输入: [1,3,5,6], 5
输出: 2
输入: [1,3,5,6], 2
输出: 1
输入: [1,3,5,6], 7
输出: 4
输入: [1,3,5,6], 0
输出: 0
方法
二分法
适用于有序数组中查找整数值。定义low为数组第一个索引,high为最后一个索引,mid为中间索引(取左)。target每次和nums[mid]比较,如果大于,则low变成mid+1;小于,high变成mid-1。
好处是时间复杂度可以抵御O(N),还不占用额外的存储空间。
时间复杂度 O(logN)
要找到target的位置,只要找到nums[pos-1]<target<=nums[pos+1]的位置即可,总结就是找到第一个大于等于target的位置,所以可以转为二分法,即不断用二分法逼近查找第一个大于等于target的下标。
class Solution35 {
public int searchInsert(int[] nums, int target) {
int length = nums.length;
int left = 0,right = length-1;
while (left <= right){
int mid = (left+right)/2;
if (target == nums[mid]){
return mid;
}
if (target > nums[mid]) {
left = mid+1;
}else if (target < nums[mid]){
right = mid-1;
}
}
return left;
}
}
暴力法
class Solution350001 {
public int searchInsert(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
if (target <= nums[i])
return i;
}
return nums.length;
}
}
合并区间
给出一个区间的集合,请合并所有重叠的区间。
输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
方法
Sweetiee 方法
2个区间的关系有6种,总结后可以变成上面3种,只需要假设 第一个区间的起始位置永远小于等于第二区间的起始位置,如果不满足,就交换这两个区间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzqwWqxT-1596708425721)(算法.assets/91d75169b1cdb15560d361f8cb7050adfe7906c955afbe8846b92d1beba8a0d7-image.png)]
class Solution56 {
public int[][] merge(int[][] intervals) {
// 先按照每个区间的起始位置进行排序
Arrays.sort(intervals,(v1,v2) -> v1[0]-v2[0]);
// 建立结果数组
int[][] res = new int[intervals.length][2];
// 个人理解是方便之后结果数组的起始位置
int idx = -1;
// 进行遍历
for (int[] interval : intervals) {
// 如果结果数组为空,或者当前区间的起始位置大于结果数组的终止位置
// 就不合并,将当前区间加入到结果数组中
if (idx == -1 || interval[0] > res[idx][1]){
res[++idx] = interval;
}else {
res[idx][1] = Math.max(res[idx][1],interval[1]);
}
}
// 返回idx+1是元素的个数
return Arrays.copyOf(res,idx+1);
}
}
贪心算法
- 前提:区间按照左端点排序
- 贪心策略:在右端点的选择中,如果产生交集,总是将右端点的数值更新成为最大的,这样就可以合并更多的区间,符合题意。
可以被合并的区间一定是有交集的区间,只需要岁所有的区间按照左端点升序排序,然后遍历。
如果当前遍历到的区间的左端点 > 结果集中最后一个区间的右端点,说明它们没有交集,此时把区间添加到结果集;
如果当前遍历到的区间的左端点 <= 结果集中最后一个区间的右端点,说明它们有交集,此时产生合并操作,即:对结果集中最后一个区间的右端点更新(取两个区间的最大值)。
class Solution560001{
public int[][] merge(int[][] intervals){
if (intervals.length<2){
return intervals;
}
// 按照区间的起始位置排序
Arrays.sort(intervals, Comparator.comparingInt(o -> o[0]));
List<int[]> res = new ArrayList<>();
res.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
int[] curInterval = intervals[i];
// 每次新遍历到的列表与当前结果集中的最后一个区间的终止断点进行比较
int[] peek = res.get(res.size()-1);
if (curInterval[0] > peek[1]){
res.add(curInterval);
}else {
peek[1] = Math.max(curInterval[1], peek[1]);
}
}
return res.toArray(new int[res.size()][]);
}
}
二维数组
只是将数组中的每个元素变成了一维数组,本质上仍然是一个一维数组,可以看成一个矩阵
旋转矩阵
给你一幅由 N × N
矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
方法
使用辅助数组
5 1 9 11 x x x 5
x x x x =旋转后=> x x x 1
x x x x x x x 9
x x x x x x x 11
x x x x x x 2 x
2 4 8 10 =旋转后=> x x 4 x
x x x x x x 8 x
x x x x x x 10 x
总结发现,第1行的第x个元素在旋转后变成倒数第1列的第x个元素。因为矩阵索引是从0开始,所以int[row][col]变为int[col][length-1-row]。借用辅助数组,可以实现,但是就不能完成题意了。
class Solution0107 {
public void rotate(int[][] matrix) {
int length = matrix.length;
int[][] newMatrix = new int[length][length];
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
newMatrix[j][length-i-1] = matrix[i][j];
}
}
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
matrix[i][j] = newMatrix[i][j];
}
}
}
}
原地旋转
旋转的效果就是将元素换位置,所以先使用一个临时变量来存储一个元素,然后把其他相对应位置的3个元素替换着赋值。,就达到原地旋转的效果。
int tmp = matrix[i][j];
matrix[i][j] = matrix[matrix.length-1-j][i];
matrix[matrix.length-1-j][i] = matrix[matrix.length-1-i][matrix.length-1-j];
matrix[matrix.length-1-i][matrix.length-1-j] = matrix[j][matrix.length-1-i];
matrix[j][matrix.length-1-i] = tmp;
但是如何判定我们要旋转多少个元素么。可以使用数学归纳法。假定一个2*2的矩阵,我们只需要平分为四部分,然后旋转四部分就行;假定一个3 * 3 的矩阵,中心位置肯定不动,其他四部分旋转。
就变成,偶数矩阵,竖着是N/2(行),横着是N/2(列);奇数矩阵,竖着是N/2(行),横着是(N+1)/2(列),横竖交换也可以。
class Solution01070001{
public void rotate(int[][] matrix){
if(matrix == null || matrix.length == 0)
return;
for(int i = 0; i < matrix.length/2; ++i){
for(int j = 0; j < (matrix.length+1)/2; ++j){
int tmp = matrix[i][j];
matrix[i][j] = matrix[matrix.length-1-j][i];
matrix[matrix.length-1-j][i] = matrix[matrix.length-1-i][matrix.length-1-j];
matrix[matrix.length-1-i][matrix.length-1-j] = matrix[j][matrix.length-1-i];
matrix[j][matrix.length-1-i] = tmp;
}
}
}
}
复杂度:
-
时间:O(N²),
-
空间:O(1)
用翻转代替旋转
int tmp = matrix[i][j];
matrix[i][j] = matrix[matrix.length-1-j][i];
matrix[matrix.length-1-j][i] = matrix[matrix.length-1-i][matrix.length-1-j];
matrix[matrix.length-1-i][matrix.length-1-j] = matrix[j][matrix.length-1-i];
matrix[j][matrix.length-1-i] = tmp;
}
}
}
}
复杂度:
-
时间:O(N²),
-
空间:O(1)