题目代号: 剑指 Offer 03. 数组中重复的数字
题目描述:
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
测试用例:
输入:
7
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
我的分析:
给它一排序,然后顺序访问就好了
代码
package 剑指offer;
import java.util.Arrays;
import java.util.Scanner;
/**
* @program: day01
* @description:这个题我为了能有效的输入,输入第一行为输入的个数
* 第二行为输入的数组
* 第二行读入数的时候sc.nextInt()一个一个的读入
* @author: Mr.Wang
* @create: 2021-04-08 16:28
**/
public class offer1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
int n = sc.nextInt();
int[] nums = new int[n];
for (int i = 1;i <= n;i++){
nums[i-1] = sc.nextInt();
}
int jieguo = findRepeatNumber(nums);
System.out.println(jieguo);
}
}
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;
}
}
**题目代号:**剑指 Offer 04. 二维数组中的查找
题目描述:
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
测试用例:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
我的分析:
从第一行进行遍历,当一旦遇到比目标值大的边界,就把这个值当作右边界,相当于缩小边界范围
代码:
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int h = matrix.length;
int l = matrix[0].length;
if(h == 0 && l == 0){
return false;
}
for(int i = 0;i < h;i++){
for(int j = 0;j < l;j++){
if(matrix[i][j] > target){
l = j;
continue;
}else if(matrix[i][j] == target){
return true;
}
}
}
return false;
}
}
题目代号: 剑指 Offer 11. 旋转数组的最小数字
题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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
我的分析:
这个题其实可以直接排序,然后输出最小值即可
同时也可以用二分法,切记,右半部分是有序的,找这里面的最小值,所以要拿中间值和右边的值来比较
代码:
class Solution {
public int minArray(int[] numbers) {
if(numbers[0] < numbers[numbers.length-1]){
return numbers[0];
}
int left = 0;
int right = numbers.length-1;
while (left < right){
int mid = (left + right)/2;
if(numbers[right] < numbers[mid]){//说明左边是有序的,那就肯定在右边
left = mid+1;//当中间值大的时候,那它肯定不是最小值
}else if(numbers[right] > numbers[mid]){
right = mid;//当中间值小的时候,那就不能跳过它了
}else {
right--;//当俩人相等的时候,就缩小范围吧
}
}
return numbers[left];
}
}
题目代号: 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
测试用例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
我的分析:
就左右互换,
代码:
class Solution {
public int[] exchange(int[] nums) {
int left = 0;
int right = nums.length-1;
int temp = 0;
while (left < right){
if(nums[left] % 2 == 1){
left++;
continue;//这个的目的是一直在左边要找到偶数,再不就一直寻找
}
if(nums[right] % 2 == 0){
right--;
continue;//这个的目的是一直在右边要找到奇数,再不就一直寻找
}
if(nums[left] % 2==0 && nums[right] %2 == 1){
temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
return nums;
}
}
题目代号: 剑指 Offer 29. 顺时针打印矩阵
题目描述:
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
测试用例:
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
我的分析:
从右向左——>判断下是否接触向下的边界
从上向下——>判断下是否接触向左的边界
从右向左——>判断下是否接触向上的边界
从下向上——>判断下是否接触向右的边界
代码:
class Solution {
public int[] spiralOrder(int[][] matrix) {
if (matrix.length == 0) {
return new int[0];
}
int[] res = new int[matrix.length*matrix[0].length];
int top = 0,left = 0,bottom = matrix.length-1,right = matrix[0].length-1;
int idx = 0;
while (true){
for (int i = left;i <= right;i++){//向右
res[idx++] = matrix[top][i];
}
if(++top > bottom){
break;
}
for(int i = top;i <= bottom;i++){//向下
res[idx++] = matrix[i][right];
}
if(--right<left){
break;
}
for(int i = right;i >= left;i--){//向左
res[idx++] = matrix[bottom][i];
}
if(--bottom<top){
break;
}
for(int i = bottom;i >= top;i--){//向上
res[idx++] = matrix[i][left];
}
if(++left > right){
break;
}
}
return res;
}
}
题目代号: 剑指 Offer 39. 数组中出现次数超过一半的数字
题目描述:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
测试用例:
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
我的分析:
我先把数组元素按键值压入map中
再看map中的哪个值是大于一半的,那就是这个值对应的键
代码:
public class offer39 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
int n = sc.nextInt();
int[] array = new int[n];
for(int i = 0;i < n;i++){
array[i] = sc.nextInt();
}
int out = majorityElement(array);
System.out.println(out);
}
}
public static Integer majorityElement(int[] nums){
Map<Integer,Integer> map = new HashMap<>();
int length = nums.length;
int jieguo = length/2+1;
for (int i = 0;i < length;i++){
map.put(nums[i],map.getOrDefault(nums[i],0)+1);//先把元素以键值对的形式压入map中
}
for(int key:map.keySet()){//以键来依次拿出来看看
if(map.get(key) >= jieguo){//键对应的值是大于结果的,那就返回键
return key;
}
}
return -1;
}
}
题目代号: 剑指 Offer 42. 连续子数组的最大和
题目描述:
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
测试用例:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
我的分析:
就是一道动态规划的题,说白了就是每当遍历一个值的时候,就看它前后的某一部分值对此时遍历值的影响是正的还是负的
如果影响是正,那就叠加上去。如果影响是负,那就不叠加了。
代码:
package 剑指offer;
import java.util.Scanner;
/**
* @program: day01
* @description:这个题的思想很重要,看来动态规划还是难理解的
* 从数组中每次遍历一个值的时候,必须要关注的是看看这个值之前的那些值对自己是正的影响还是负的影响
* 正的影响就+上,负的影响就减去
* @author: Mr.Wang
* @create: 2021-04-10 16:53
**/
public class offer42 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
int n = sc.nextInt();
int[] array = new int[n];
for(int i = 1;i <= n;i++){
array[i-1] = sc.nextInt();
}
System.out.println(maxSubArray(array));
}
}
public static int maxSubArray(int[] nums){
int res = nums[0];
for(int i = 1;i < nums.length;i++){
nums[i] += Math.max(nums[i-1],0);
res = Math.max(res,nums[i]);
}
return res;
}
}
题目代号: 剑指 Offer 45. 把数组排成最小的数
题目描述:
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
测试用例:
示例 1:
输入: [10,2]
输出: “102”
示例 2:
输入: [3,30,34,5,9]
输出: “3033459”
我的分析:
先把数组遍历放进String类型的数组中,让int类型转化成String类型
然后将里面的字符串两个两个拿出来比较,让小的放在前面
把这个数组中的String类型元素拿出来,放进StringBuilder里面,这样就连起来了
代码:
package 剑指offer;
import java.util.Arrays;
import java.util.Scanner;
/**
* @program: day01
* @description:
* @author: Mr.Wang
* @create: 2021-04-10 20:46
**/
public class offer45 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
int n = sc.nextInt();
int[] arr = new int[n];
for(int i = 0;i < arr.length;i++){
arr[i] = sc.nextInt();
}
System.out.println(minNumber(arr));
}
}
public static String minNumber(int[] nums) {
String[] str = new String[nums.length];
for(int i = 0;i < str.length;i++){
str[i] = String.valueOf(nums[i]);//将其他数据类型转化为字符串类型
}
Arrays.sort(str,(x,y) -> (x+y).compareTo(y+x));//记住,这就是升序
/* 这句话的意思就是:
x+y > y+x 返回1
x+y = y+x 返回0
x+y < y+x 返回-1
*/
StringBuilder res = new StringBuilder();
for(String s:str){
res.append(s);//就把字符串在末尾依次添加,连成一个串
}
return res.toString();
}
}
题目代号: 51 数组中的逆序对
题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
测试用例:
输入: [7,5,6,4]
输出: 5
我的分析:
就归并排序就好了,在合并的过程中,有多少次交换,就有多少个逆序对
代码:
int count = 0;
public int reversePairs(int[] nums) {
sort(nums, 0, nums.length - 1);
return count;
}
public void sort(int[] nums,int left,int right){
if(left < right){
int mid = (left+right) / 2;//开始递归划分
sort(nums,left,mid);//归并排序左部分(left,mid)
sort(nums,mid+1,right);//归并排序右部分(mid+1,right)
merge(nums,left,mid,right);//合并
}
}
private void merge(int[] nums, int left, int mid, int right) {
int[] temp = new int[right - left + 1];//结果数组
int i = left;//左部分首元素[left,mid]
int j = mid + 1;//右部分首元素[mid+1,right]
int t = 0;
while(i <=mid && j <=right){//在范围之内
if(nums[i] <= nums[j]){
temp[t++] = nums[i++];
}else{
count += (mid - i + 1);//只需要这行代码,需要前后交换的时候
//当 nums[i] > nums[j]的时候统计: [i, ..., mid]的元素均大于[j]位置上的元素, 此时产生的逆序对个数为 mid-i+1
temp[t++] = nums[j++];
}
}
while (i <= mid){//右边遍历完事了 左边还剩呢
temp[t++] = nums[i++];
}
while( j <= right){//左边遍历完事了 右边还剩了
temp[t++] = nums[j++];
}
t = 0;//将temp中的元素 全部都copy到原数组里边去
while (left <=right){
nums[left++] = temp[t++];
}
}
题目代号: 剑指 Offer 56 - I. 数组中数字出现的次数
题目描述:
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。
测试用例:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
我的分析:
遍历数组元素,先都添加到map中
再遍历map,把值为value为1的key拿出来
代码:
public int[] singleNumbers(int[] nums) {
int[] res = new int[2];
int r = 0;
Map<Integer,Integer> map = new HashMap<>();//数字+次数
for(int i = 0;i < nums.length;i++){
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
int value = entry.getValue();
if(value == 1){
res[r++] = entry.getKey();
}
}
return res;
}
题目代号: 剑指 Offer 56 - II. 数组中数字出现的次数 II
题目描述:
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
测试用例:
输入:nums = [9,1,7,9,7,9,7]
输出:1
我的分析:
跟上一道题一样的,只是结果只有一个数,这样更简单
代码:
int res = 0;
int r = 0;
Map<Integer,Integer> map = new HashMap<>();//数字+次数
for(int i = 0;i < nums.length;i++){
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
int value = entry.getValue();
if(value == 1){
res = entry.getKey();
break;
}
}
return res;
题目代号: 剑指 Offer 53 - I. 在排序数组中查找数字 I
题目描述:
统计一个数字在排序数组中出现的次数。
测试用例:
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
我的分析:
先遍历数组放进map中
再从map中取键对应的值
代码:
public int search(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i < nums.length;i++){
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
int res = 0;
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
if(entry.getKey()==target){
res = entry.getValue();
}
}
return res;
}
题目代号: 剑指 Offer 53 - II. 0~n-1中缺失的数字
题目描述:
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
测试用例:
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
我的分析:
就依次遍历数组就好了,用一个res跟着动,多会断了,就返回这个res就好了
代码:
public int missingNumber(int[] nums) {
int res = 0;
if(nums[nums.length-1] != nums.length){
res = nums.length;
}
for(int i = 0;i < nums.length;i++){
if(nums[i] == res){
res++;
continue;
}else {
break;
}
}
return res;
}
题目代号: 剑指 Offer 66. 构建乘积数组
题目描述:
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
测试用例:
输入:
[2, 3, 4, 5, 6]
输出 :
[360,240,180,144,120]
left :
[1,2,6,24,120]
right :
[360,120,30,6,1]
我的分析:
每个位置存放了左边乘积和右边乘积,不包括它自己的哦
这样左边乘积乘右边乘积即可
代码:
public int[] constructArr(int[] a) {
if (a == null || a.length == 0) return a;
int len = a.length;
int[] left = new int[len];
int[] right = new int[len];
left[0] = right[len - 1] = 1;//边界值肯定是1,因为一会有数要乘它
for (int i = 1; i < len; i++) {
left[i] = left[i - 1] * a[i - 1];//每个位置最后存放的是不包括这个位置的左半部分乘积
}
for (int i = len - 2; i >= 0; i--) {
right[i] = right[i + 1] * a[i + 1];//每个位置最后存放的是不包括这个位置的右半部分乘积
}
int[] ans = new int[len];
for (int i = 0; i < len; i++) {
ans[i] = left[i] * right[i];//左半部分乘右半部分
}
return ans;
}
题目代号: 剑指 Offer 63. 股票的最大利润
题目描述:
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
测试用例:
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
我的分析:
先挑出一个最小值,不断更新,再用当前值去减最小值,不断更新,这样出来肯定就是利润最大值了
代码:
public int maxProfit(int[] prices) {
int n = prices.length;
int minnum = Integer.MAX_VALUE;//记录最小值
int sell = 0;//记录卖出的最大值
for (int i = 0; i < n; ++i) {
minnum = Math.min(minnum, prices[i]);//找前i个元素中最小的
sell = Math.max(sell, prices[i] - minnum);
}
return sell;
}