剑指Offer刷题—数组类
剑指Offer刷题——数组类
持续更新...好好总结,早日刷完!
03,easy
找出数组中重复的数字
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
输入:
[2, 3, 1, 0, 2, 5, 3] |
输出:2 或 3
方法一. 利用Arrays.Sort()方法排序,比较相邻两个数字
public static int findRepeatNumber(int []nums){
Arrays.Sort(nums);
for(int i = 0; i< nums.length; i++){
if(nums[i] == nums[i+1])
return nums[i];
}
return -1;
}方法二. 把数组元素赋给新数组的索引,如果个数>1,则返回-1
public static int findRepeatNumber(int []nums){
int[] newNum = new int[nums.length];
for(int i : nums){
if(++newNum[i] > 1)
return i;
}
return -1;
}方法三. 新建Set集合,利用Set的无序不可重复性,如果不能添加此元素,说明重复,返回此元素
public static int findRepeatNumber(int []nums){
HashSet<Integer> set = new HashSet<>();
for(int i : nums){
if(!set.add(i))
return i;
}
return -1;
}
04,middle
二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[ |
给定 target = 5,返回 true。
给定 target = 20,返回 false。
判空。matrix和其行列均不能为空
方法一:暴力查询。遍历二维数组,直到找到相同整数,返回true;或遍历完也没有找到,返回false。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix)
int rows = matrix.length;//行数
int columns = matrix[0].length;//列数
//遍历此二维数组
for(int i = 0; i < rows; i++){
for(int j = 0; j < columns; j++){
if(target == matrix[i][j])
return true;
}
}
return false;
}
}方法二:
- 思路:由此数组从左向右递增,从上向下递增的规律,比较target与右上角的数字,如果比它小(大),则在左(右)边找,再与下一数字比较...直到找到相同整数,返回true;或遍历完也没有找到,返回false。
- 边界条件:遍历的行列指针i,j不能超过数组的行数rows,列数columns
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix == null|| matrix.length == 0|| matrix[0].length == 0) return false;
int rows = matrix.length;//行数
int columns = matrix[0].length;//列数
//从右上角开始比较
int i = 0, j = columns - 1;
//边界条件
while(i < rows && j >= 0){
//target比此二维数组元素大,向下找,行指针++
if(target > matrix[i][j]) i++;
//target比此二维数组元素小,向左找,列指针--
else if(target < matrix[i][j]) j--;
//相等
else return true;
}
return false;
}
}
11,easy
旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 示例 1: 输入:[3,4,5,1,2] 输出:1
示例 2: 输入:[2,2,2,0,1] 输出:0
方法一:逐项查找,不考虑旋转。
public int minArray(int[] numbers) {
int index = 0;
for(int i = 0;i < numbers.length; i++){
if(numbers[index] > numbers [i]){
index = i;
}
}
return numbers[index];
}方法二:二分查找(减治思想),考虑旋转,比较nums[mid]与nums[right]。
public int minArray(int[] numbers) {
int left = 0;
int right = numbers.length - 1;
while(left < right){
int middle = (left + right) / 2;
//中间值大于最右边的值,说明旋转之后最小数字在mid右面
if(numbers[middle] > numbers[right]){
left = middle + 1;
}
//中间值小于最右边的值,说明旋转之后最小的数字在mid或mid的左边
//如[4,5,1⭐m,2,3] [5,1⭐,2m,3,4]
else if(numbers[middle] < numbers[right]){
right = middle ;
}
//中间值与最右边的值相等,不能判断最小数字在哪一边,可以缩小范围,right--
else
right--;
}
return numbers[right];//此时left,right指向同一数字
}
思考:为什么不能用最左边的值和middle比较?
举例:[3, 4, 5, 1, 2] 与 [1, 2, 3, 4, 5] ,此时,中间位置的值都比左边大,但最小值一个在后面,一个在前面,因此这种做法不能有效地减治。
12,middle
矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 :
输入:board = [ |
方法一:回溯算法。(DFS+剪枝)
- 深度优先遍历矩阵中所有字符串可能性。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
- 剪枝: 在搜索中,遇到 这条路不可能和目标字符串匹配成功 的情况(例如:此矩阵元素和目标字符不同、此元素已被访问),则应立即返回,称之为 可行性剪枝 。
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[0].length; j++){
//找回words的第0个元素
if(dfs(board,words,i,j,0))
return true;
}
}
return false;
}
private boolean dfs(char[][] board, char[] word, int i, int j, int k) {
//board[i][j]表示矩阵元素,k表示字符串words的第k个元素
//① 行或列索引越界 或 ② 当前矩阵元素与目标字符不同 或 ③ 当前矩阵元素已访问过 (③ 可合并至 ② ) 。
if(i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != word[k])
return false;
//字符串 word 已全部匹配,即 k = len(word) - 1 。
if(k == word.length- 1)
return true;
//将 board[i][j] 值暂存于变量 tmp ,并修改为字符 '/' ,
// 代表此元素已访问过,防止之后搜索时重复访问。
char temp = board[i][j];
board[i][j] = '/';
//从当前坐标的上下左右四个方向查找,只要有一个能查找到,就返回true
boolean res = dfs(board, word,i + 1, j, k + 1) || dfs(board, word,i - 1, j, k + 1)|| dfs(board, word ,i ,j - 1, k + 1) || dfs(board, word, i, j + 1, k + 1);
//恢复其本身值
board[i][j] = temp;
return res;
}
21,easy
调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4] |
方法一:遍历数组,因为不要求数字顺序,从数组索引为0开始++ 保存奇数,从末位开始-- 保存偶数。
public int[] exchange(int[] nums) {
int l = nums.length - 1;int m = 0;
int[] res = new int[nums.length];
for(int i = 0; i < nums.length; i++){
//如果为偶数
if(nums[i] % 2 == 0){
res[l--] = nums[i];
}
else{
res[m++] = nums[i];
}
}
return res;
}
错误点:在for循环中i < l + 1
的写法是错误的,因为l在循环体中会变化。
方法二:双指针left,right。若为奇数,left++;若为偶数,right--。否则nums[left]和nums[right]交换。
public int[] exchange(int[] nums) {
int left = 0; int right = nums.length - 1;
while(left < right){
if(nums[left] % 2 != 0){
left++;
}
if(nums[right] % 2 == 0){
right--;
}
else{
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
return nums;
}
39,easy
数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] |
方法一:先进行排序,若存在这样的数字,排序后一定位于数组中间位置。
public int majorityElement(int[] nums) {
// //数组先排序再找出现次数超过数组长度一半的数字
Arrays.sort(nums);//1, 2, 2, 2, 2, 2, 3 ,4 , 5
// for(int i = 0; i < nums.length; i++){
// if(nums[i] == nums[i + nums.length / 2] ){
// return nums[i];
// }
// }
// return -1;
int l = nums.length;
return nums[l/2];
}方法二:借助map,key保存数组元素,value保存出现次数。遍历数组元素,如果keySet中包含此元素,用map.get(key)得到此时的value,并更新(+1),判断若此时value >= 数组长度的一半,返回此时的key;如果keySet中不包含此元素,加入map且使value为1。
public int majorityElement(int[] nums) {
if(nums.length == 1) return nums[0];
//用hashMap实现,key保存数组元素,value保存出现次数
Map<Integer,Integer> map = new HashMap<>();
int value = 0;
for(int i = 0; i < nums.length; i++){
if(!map.containsKey(nums[i])){
map.put(nums[i], 1);
}
else {
value = map.get(nums[i]);
map.put(nums[i], value + 1);
if(value + 1 > nums.length / 2) {
return nums[i];
}
}
}
return -1;
}
57,easy
和为s的两个数字
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
输入:nums = [2,7,11,15], target = 9 |
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40 |
方法一:左右双指针。
- 如果两指针指向数字和 = target,返回这个数字构成的数组。
- ... < .... ,left 向右移。
- ... > .... ,right 向左移。
public int[] twoSum(int[] nums, int target) {
int left = 0; int right = nums.length - 1;
if(nums[left] >= target) return new int[0];
while(nums[right] > target){
right--;
}
while(left < right){
if(nums[left] + nums[right] == target) return new int[]{nums[left], nums[right]};
else if(nums[left] + nums[right] > target) right--;
else left++;
}
return new int[0];
}
注意: 数组遍历都可以用双指针想一想
双指针固定套路:while(left < right)
if判断中先判断 == 再判断 < , >,有助于减少运行时间
方法二:利用set的不可重复性,遍历数组将数组元素添加到set,如果set包含target - i,则返回两个元素构成的数组。
public int[] twoSum(int[] nums, int target) {
Set<Integer> set = new HashSet<>();
for (int i : nums) {
set.add(i);
if (set.contains(target - i)) {
return new int[]{i, target - i};
}
}
return new int[0];
}