目录
数组
基础知识
数组会用一些名为 索引
的数字来标识每项数据在数组中的位置,且在大多数编程语言中,索引是从 0
算起的。我们可以根据数组中的索引,快速访问数组中的元素。
常见的语言中,C++和Java中 数组的元素类型必须保持一致,但是python中可以不同,是list。
其次,数组中的元素在内存中是连续存储的,每个元素占有相同大小的内存空间。
列表中是没有索引的,这是数组和列表的最大区别。
常见题目
寻找数组的中心索引
给你一个整数数组 nums ,请计算数组的 中心下标 。数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1
- 思路
- 走到我这里,我左面的和等于剩下数字的累加和,就满足当前的需求
public int pivotIndex(int[] nums) {
if(nums == null || nums.length == 0)
return -1;
int sum = 0;
for(int i = 0; i < nums.length; i ++){
sum += nums[i];
}
int leftSum = 0;
for(int i = 0; i < nums.length; i ++){
if(leftSum * 2 == (sum - nums[i]))
return i;
else
leftSum += nums[i];
}
return -1;
}
搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
- 思路
- 很明显,要求二分法。不过有一个需要注意的就是边界值!!!当小于最左或者大于最右的时候考虑好!!
class Solution {
public int searchInsert(int[] nums, int target) {
if(nums == null || nums.length == 0)
return -1;
if(target <= nums[0])
return 0;
if(target > nums[nums.length - 1])
return nums.length;
if(target == nums[nums.length - 1])
return nums.length - 1;
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}
// 这里面不会有越界的情况 因为已经在前面判断过了
if(target > nums[mid] && target < nums[mid + 1]){
return mid + 1;
}
if(nums[mid] > target){
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1;
}
}
合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
- 思路
- 先对二维数组的0位置元素作为参照,进行升序排序,以进行区间之间的聚合
- 如果当前位置的[1]元素大等于后面的[0]位置元素,就需要进行合并
- ⚠️!!!:需要把扩在一起的区间们的[1]做个比较,让最大的来守护大家
- 就比如说:[[1, 10], [2, 3], [4, 5]] 如果只是用当前的和下一个比的话,那么3比4小,就会得出[[1, 10], [4, 5]]这样的结果,[4, 5]没有被囊括进来,所以需要一个变量来迭代这个守护线
class Solution {
public int[][] merge(int[][] intervals) {
if(intervals == null || intervals.length == 0)
return null;
Arrays.sort(intervals, (o1, o2) -> {
if(o1[0] == o2[0])
return o1[1] - o2[1];
else
return o1[0] - o2[0];
});
List<int[]> res = new ArrayList<>();
int i = 0;
while(i < intervals.length){
int[] list = new int[2];
list[0] = intervals[i][0];
// 这个tmp就是守护线
int tmp = intervals[i][1];
// 下面比较的时候,比较的也是tmp,比tmp小的就进来
while((i < intervals.length - 1) && (tmp >= intervals[i + 1][0])){
i ++;
// 在这里迭代
tmp = Math.max(tmp, intervals[i][1]);
}
list[1] = tmp;
i ++;
res.add(list);
}
return res.toArray(new int[res.size()][2]);
}
}
合并两个无序数组并去重排序
题目描述:两个无序的数组,请合并他们并去重排序
思路:
- 对两个数组分别排序,把排序方法抽出为一个方法
- 再合并到一个列表里,注意去重
代码实现:
public Integer[] generateOneArray(Integer[] array1, Integer[] array2){
if(array1 != null && array1.length > 0){
//排序
sort(array1, 0, array1.length - 1);
}
if(array2 != null && array2.length > 0){
//排序
sort(array2, 0, array2.length - 1);
}
System.out.println(Arrays.toString(array1));
System.out.println(Arrays.toString(array2));
List<Integer> res = new ArrayList<>();
Integer index1 = 0, index2 = 0;
while(index1 < array1.length && index2 < array2.length){
// 先放入小的
if(array1[index1] < array2[index2]){
if(! res.contains(array1[index1]))
res.add(array1[index1 ++]);
else
index1 ++;
}else{
if(! res.contains(array2[index2]))
res.add(array2[index2 ++]);
else
index2 ++;
}
}
while(index1 < array1.length){
if(! res.contains(array1[index1]))
res.add(array1[index1 ++]);
else
index1 ++;
}
while(index2 < array2.length){
if(! res.contains(array2[index2]))
res.add(array2[index2 ++]);
else
index2 ++;
}
return res.toArray(new Integer[res.size()]);
}
private void sort(Integer[] array, Integer left, Integer right){
Integer mid = left + (right - left) / 2;
if(left < right){
sort(array, left, mid);
sort(array, mid + 1, right);
merge(array, left, mid, right);
}
}
private void merge(Integer[] array, Integer left, Integer mid, Integer right){
Integer[] temp = new Integer[right - left + 1];
Integer index1 = left, index2 = mid + 1;
Integer k = 0;
while(index1 <= mid && index2 <= right){
if(array[index1] < array[index2]){
temp[k ++] = array[index1 ++];
}else{
temp[k ++] = array[index2 ++];
}
}
while(index1 <= mid){
temp[k ++] = array[index1 ++];
}
while(index2 <= right){
temp[k ++] = array[index2 ++];
}
for(int j = 0; j < temp.length; j ++){
array[left + j] = temp[j];
}
}
字符串
常见题目
最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""
。
- 思路
- 从前向后迭代,挨个比较两个字符串的公共前缀
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length < 1)
return "";
if(strs.length == 1)
return strs[0];
String res = strs[0];
int i = 1;
while(i < strs.length){
int j = res.length();
int k = strs[i].length();
res = res.substring(0, Math.min(j, k));
for(int p = 0; p < Math.min(j, k); p ++){
if(res.charAt(p) != strs[i].charAt(p)){
res = res.substring(0, p);
break;
}
}
i ++;
}
return res;
}
}
最长回文子串
- 思路
- 先把给定的字符串翻转
- 对两个字符串进行比较,通过dp,迭代相等的子串的大小
- 当子串长度大于当前的最大长度的时候,需要判断「翻转字符串的镜面对称 + 当前相等子串的长度 == i的位置」,再进行迭代
class Solution {
public String longestPalindrome(String s) {
if(s == null || s.length() == 0)
return "";
String revers = new StringBuilder(s).reverse().toString();
int[] arr = new int[s.length()];
int maxLen = 0;
int endX = 0;
for(int i = 0; i < s.length(); i++){
for(int j = s.length() - 1; j >= 0; j --){
if(s.charAt(i) == revers.charAt(j)){
if(j == 0){
arr[j] = 1;
}else{
arr[j] = arr[j - 1] + 1;
}
}else{
arr[j] = 0;
}
if(arr[j] > maxLen){
int before = s.length() - 1 - j;
if(before + arr[j] - 1 == i){
maxLen = arr[j];
endX = i;
}
}
}
}
return s.substring(endX - maxLen + 1, endX + 1);
}
}
双指针
基础知识
一般是两种方式:
- 相反的方向,同样的速度,直到相遇/到达两边的边界
- 相同的方向,不同的速度,快指针向前走,满足一定条件的时候慢指针才更新
常见题目
长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
- 思路
- 快指针先移动,当当前的累加和超过target的时候,计算左右边界的区间大小
- 这个时候,左指针向前走(缩减当前的累加和),当当前的累加和还是超过target,左指针继续向前走,缩小左右区间的大小
- 当小于target的时候,右指针开始向后移动,继续找到新的满足条件的区间大小
- 快指针先移动,当当前的累加和超过target的时候,计算左右边界的区间大小
- 代码
class Solution {
public int minSubArrayLen(int target, int[] nums) {
if(nums == null || nums.length == 0 || target <= 0)
return -1;
int res = Integer.MAX_VALUE;
int slower = 0, faster = 0;
int cur = 0;
while(faster < nums.length){
// 叠加累加和
cur = cur + nums[faster];
while(cur >= target){
res = Math.min(res, faster - slower + 1);
// 缩减累加和
cur = cur - nums[slower];
// 左指针向后移动,减小当前的区间
slower ++;
}
// 右指针向后移动
faster ++;
}
return res == Integer.MAX_VALUE ? 0 : res;
}
}
二分法
模板一
-
初始条件:left = 0, right = length-1
-
终止:left > right
-
向左查找:right = mid-1
-
向右查找:left = mid+1
常见题目
x的平方根
- 题目
-
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
-
-
思路
-
二分法,迭代计算mid*mid, 比较和x的大小
-
两个注意事项:
-
第一个:乘积溢出
-
第二个:循环截止的条件是left>right,所以遇到小数的时候,应该返回right,right是退去小数后的整数
-
-
-
代码
class Solution {
public int mySqrt(int x) {
if(x < 2)
return x;
int left = 1, right = x;
while(left <= right){
// 一定要以这种方式,left+right/2 的方式会造成溢出
int mid = left + (right - left) / 2;
long res = (long)mid * mid;
if(res == x){
return mid;
}else if(res < x){
left = mid + 1;
}else
right = mid - 1;
}
return right;
}
}
搜索排序旋转数组
- 题目
-
整数数组 nums 按升序排列,数组中的值 互不相同 。在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
-
-
思路
-
二分法
-
要么左半边有序,要么右半边有序,以这个角度去搜索
-
-
代码
class Solution {
public int search(int[] nums, int target) {
if(nums == null)
return -1;
int left = 0, right = nums.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] >= nums[left]){
if((nums[mid] > target) && (target >= nums[left])){
right = mid - 1;
}else{
left = mid + 1;
}
}else{
// 右边递增
if((nums[mid] < target) && (target <= nums[right])){
left = mid + 1;
}else{
right = mid - 1;
}
}
}
return -1;
}
}
模板二
- 初始条件:left = 0, right = length
- 终止:left == right
- 向左查找:right = mid
- 向右查找:left = mid+1
常见题目
旋转排序数组中的最小值
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
- 思路
- log(n)很明显二分
- 然后如果中间的元素大于后面的元素,很明显甩过去了;否则还在前面
- 代码
class Solution {
public int findMin(int[] nums) {
if(nums == null || nums.length == 0)
return -1;
if(nums.length == 1)
return nums[0];
int left = 0, right = nums.length - 1;
while(left < right){
int mid = left + (right - left) / 2;
// 说明肯定在右面
if(nums[mid] > nums[right]){
left = mid + 1;
}else{ //当前的位置也需要考虑
right = mid;
}
}
return nums[right];
}
}