当前位置: 首页 > news >正文

leetcode--各种数据结构相关的题

数据结构

  • 1.数组
    • (1)找到所有数组中消失的数字(448)
    • (2)旋转图像(48)
    • (3)搜索二维矩阵 II(240)
    • (4)最多能完成排序的块(769)
  • 2.栈和队列
    • (1)用栈实现队列(232)
    • (2)最小栈(155)
    • (3)有效的括号(20)
  • 3.单调栈
    • (1)每日温度
  • 4.优先队列
    • (1)合并K个升序链表(23)
  • 5.双端队列
    • (1)滑动窗口最大值(239)
  • 6.哈希表
    • (1)两数之和(1)
    • (2)最长连续序列(128)
    • (3)直线上最多的点数
  • 7.前缀和与积分图
    • (1)区域和检索 - 数组不可变(303)
    • (2)二维区域和检索 - 矩阵不可变(304)
    • (3)和为 K 的子数组(560)
  • 8.练习
    • (1)重塑矩阵(566)
    • (2)用队列实现栈(225)
    • (3)下一个更大元素 II(503)
    • (4)存在重复元素(217)
    • (5)数组的度
    • (6)最长和谐子序列(594)
    • (7)寻找重复数(287)
    • (8)优势洗牌(870)

1.数组

(1)找到所有数组中消失的数字(448)

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]

输入:nums = [1,1]
输出:[2]
//时间空间复杂度都为O(n)
public class Solution {

    public static void main(String[] args) {
        int[] arr={4,3,2,7,8,2,3,1};
        Solution solution=new Solution();
        System.out.println(solution.findDisappearedNumbers(arr));
    }

    public List<Integer> findDisappearedNumbers(int[] nums) {
        int n= nums.length;
        boolean[] find=new boolean[n+1];//数组中出现的数将该数对应的数组位置置为true
        List<Integer> list=new ArrayList<>();
        for (int i=0;i<n;i++){
            find[nums[i]]=true;
        }
        for (int i=1;i<=n;i++){
            if (!find[i]){
                list.add(i);
            }
        }
        return list;
    }
}


//空间复杂度为O(1)
public class Solution {

    public static void main(String[] args) {
        int[] arr={4,3,2,7,8,2,3,1};
        Solution solution=new Solution();
        System.out.println(solution.findDisappearedNumbers(arr));
    }

    public List<Integer> findDisappearedNumbers(int[] nums) {
        int n= nums.length;
        List<Integer> list=new ArrayList<>();
        //遍历数组 每遇到一个数x 就让nums[x-1]增加n
        //当我们遍历到某个位置时 其中的数可能已经被增加过了 因此需要对n取模
        for (int i=0;i<n;i++){
            int x=(nums[i]-1)%n;
            nums[x]+=n;
        }
        //遍历nums 若nums[i]未大于n 就说明没有遇到过数i+1
        for (int i=0;i<n;i++){
            if (nums[i]<=n){
                list.add(i+1);
            }
        }
        return list;
    }
}

(2)旋转图像(48)

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。


对应位置交换位置,结束条件可以考虑i和j同时向内缩进,也就是一圈一圈考虑
public class Solution {

    public static void main(String[] args) {
        int[][] arr={{5,1,9,11},
                     {2,4,8,10},
                     {13,3,6,7},
                     {15,14,12,16}};
        Solution solution=new Solution();
        solution.rotate(arr);
        for (int i=0;i<arr.length;i++){
            for (int j=0;j< arr[0].length;j++){
                System.out.print(arr[i][j]+" ");
            }
            System.out.println();
        }
    }

    public void rotate(int[][] matrix) {
        int rowMin=0;//从第0行开始 起始行
        int colMin=0;//从第0列开始 起始列
        int rowMax= matrix.length-1;//第一圈可以用到的的最大行数是总行数-1
        int colMax=matrix[0].length-1;//第一圈可以用到的的最大列数是总列数-1
        //旋转完一圈开始下一圈 循环退出条件是 起始行>最大行 起始列>最大列
        while (rowMin<=rowMax&&colMin<=colMax){
            RotateOneCircle(matrix,rowMin,colMin,rowMax,colMax);
            //缩进
            rowMin++;
            colMin++;
            rowMax--;
            colMax--;
        }
    }

    //旋转一圈的方法
    private void RotateOneCircle(int[][] arr, int rowMin, int colMin, int rowMax, int colMax) {
        int temp=0;//辅助数
        //遍历旋转这一圈
        for (int j=0;j<colMax-colMin;j++){
            temp=arr[rowMin][colMin+j];
            arr[rowMin][colMin+j]=arr[rowMax-j][colMin];
            arr[rowMax-j][colMin]=arr[rowMax][colMax-j];
            arr[rowMax][colMax-j]=arr[rowMin+j][colMax];
            arr[rowMin+j][colMax]=temp;
        }
    }
}

(3)搜索二维矩阵 II(240)

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

输入:matrix = [[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]], target = 5
输出:true
从数组的右上角开始遍历,如果target大于当前元素,则用当前元素下方的元素再次比较;如果target小于当前元素,
则用当前元素左边的元素再次比较;以此类推,知道找到target或者到达边界
public class Solution {

    public static void main(String[] args) {
        int[][] arr={{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}};
        int target=5;
        Solution solution=new Solution();
        System.out.println(solution.searchMatrix(arr,5));
    }

    public boolean searchMatrix(int[][] matrix, int target) {
        int m= matrix.length;
        int n=matrix[0].length-1;//表示列
        int i=0;//表示行
        while (i<m&&n>=0){
            if (target>matrix[i][n]){//target大于当前元素 比较下方元素
                i++;
            } else if (target<matrix[i][n]) {//target小于当前元素 比较左方元素
                n--;
            }else {//找到target
                return true;
            }
        }
        return false;
    }
}

(4)最多能完成排序的块(769)

给定一个长度为 n 的整数数组 arr ,它表示在 [0, n - 1] 范围内的整数的排列。
我们将 arr 分割成若干 块 (即分区),并对每个块单独排序。将它们连接起来后,使得连接的结果和按升序排序后的原数组相同。
返回数组能分成的最多块数量。

输入: arr = [4,3,2,1,0]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。

输入: arr = [1,0,2,3,4]
输出: 4
解释:
我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。
对每个块单独排序后,结果为 [0, 1], [2], [3], [4]
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,0,2,3,4};
        Solution solution=new Solution();
        System.out.println(solution.maxChunksToSorted(arr));
    }

    public int maxChunksToSorted(int[] arr) {
        int n=arr.length;
        if (arr.length==1){
            return 1;
        }
        int maxCount=0;//可以分成的最大块数
        int preMax=0;//当前最大值
        for (int i=0;i<n;i++){
            preMax=Math.max(preMax,arr[i]);
            //如果当前最大值等于当前的i值
            if (preMax==i){
                maxCount++;//可以分块数+1
            }
        }
        return maxCount;
    }
}

2.栈和队列

(1)用栈实现队列(232)

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:

你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

public class MyQueue {

    Stack<Integer> stack1;
    Stack<Integer> stack2;

    public MyQueue() {
        stack1=new Stack<>();
        stack2=new Stack<>();
    }

    public void push(int x) {
        while (!stack2.isEmpty()){
            stack1.push(stack2.pop());
        }
        stack1.push(x);
    }

    public int pop() {
        if (stack2.isEmpty()){
            while (!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }

    public int peek() {
        if (stack2.isEmpty()){
            while (!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();
    }

    public boolean empty() {
        if (stack1.empty()&& stack2.empty()){
            return true;
        }
        return false;
    }
}


public class QueueTest {

    public static void main(String[] args) {
        MyQueue queue=new MyQueue();
        queue.push(1);
        queue.push(2);
        queue.push(3);
        //System.out.println("pop():"+queue.pop());
        System.out.println("peak():"+queue.peek());
        System.out.println("empty():"+queue.empty());
        queue.push(4);
        System.out.println("====================");
        System.out.println("pop():"+queue.pop());
        System.out.println("peak():"+queue.peek());
        System.out.println("empty():"+queue.empty());
    }

}

(2)最小栈(155)

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。

public class MinStack {

    Stack<Integer> stack;//普通栈
    Stack<Integer> minStack;//返回最小值的栈

    public MinStack() {
        stack=new Stack<>();
        minStack=new Stack<>();
        minStack.push(Integer.MAX_VALUE);
    }

    public void push(int val) {
        stack.push(val);
        //最小值栈保存当前元素加进来后 栈中的最小值
        minStack.push(Math.min(minStack.peek(),val));
    }

    public void pop() {
        stack.pop();
        //当前元素出栈时 最小值栈对应的位置也要出栈
        minStack.pop();
    }

    public int top() {
        return stack.peek();
    }

    public int getMin() {
        return minStack.peek();
    }

}


public class MinStackTest {

    public static void main(String[] args) {
        MinStack stack=new MinStack();
        stack.push(-3);
        stack.push(1);
        stack.push(23);
        System.out.println(stack.getMin());
    }

}

(3)有效的括号(20)

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

输入:s = "()"
输出:true

输入:s = "()[]{}"
输出:true

输入:s = "(]"
输出:false
public class Solution {

    public static void main(String[] args) {
        String s="()[]{}";
        Solution solution=new Solution();
        System.out.println(solution.isValid(s));
    }

    public boolean isValid(String s) {
        int n=s.length();
        //长度为奇数直接返回false
        if (n%2!=0){
            return false;
        }
        Stack<Character> stack=new Stack<>();
        Map<Character,Character> map=new HashMap<>();
        //将右括号和左括号分别以 key 和 value 存储
        map.put(')','(');
        map.put('}','{');
        map.put(']','[');
        //遍历字符串匹配括号
        for (int i=0;i<n;i++){
            char ch=s.charAt(i);
            if (map.containsKey(ch)){//如果ch是map集合里的key 也就是右括号
                //先判断stack是否为空或者栈顶元素是否与ch匹配
                if (stack.isEmpty()||stack.peek()!=map.get(ch)){
                    return false;
                }
                //如果匹配
                stack.pop();//弹出栈顶括号
            }else {//如果ch是左括号
                stack.push(ch);
            }
        }
        return stack.isEmpty();
    }
}

3.单调栈

(1)每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]

输入: temperatures = [30,60,90]
输出: [1,1,0]
public class Solution {

    public static void main(String[] args) {
        int[] arr={73,74,75,71,69,72,76,73};
        Solution solution=new Solution();
        int[] tem = solution.dailyTemperatures(arr);
        for (int i=0;i< tem.length;i++){
            System.out.print(tem[i]+" ");
        }
    }

    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        if (n==1){
            return new int[1];
        }
        int[] arr=new int[n];
        Stack<Integer> stack=new Stack<>();
        for (int i=0;i<n;i++){
            //当栈不为空并且栈顶温度小于当前温度时
            while (!stack.isEmpty()&&temperatures[i]>temperatures[stack.peek()]){
                arr[stack.peek()]=i-stack.pop();
            }
            //当上边两个条件不全满足时 将当前元素的下标入栈
            stack.push(i);
        }

        return arr;
    }
}

4.优先队列

(1)合并K个升序链表(23)

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

输入:lists = []
输出:[]

输入:lists = [[]]
输出:[]
//顺序遍历合并每一个链表
public class Solution {

    public ListNode mergeKLists(ListNode[] lists) {
        ListNode list=null;//需要返回的链表
        //遍历每一个链表 然后与list合并
        for (int i=0;i< lists.length;i++){
            list=mergeTwoLists(lists[i],list);
        }
        return list;
    }

    private ListNode mergeTwoLists(ListNode a,ListNode b){
        if (a==null||b==null){
            return a!=null?a:b;
        }
        ListNode head=new ListNode(0);//设置一个头节点 返回的时候返回他的next节点
        ListNode tail=head;//记录下一个插入位置的前一个位置
        ListNode preA=a;//a链表的当前节点
        ListNode preB=b;//b链表的当前节点
        while (preA!=null&&preB!=null){
            if (preA.val<preB.val){
                tail.next=preA;
                preA=preA.next;
            }else {
                tail.next=preB;
                preB=preB.next;
            }
            tail=tail.next;
        }
        tail.next=preA!=null?preA:preB;
        return head.next;
    }
}


//采用分治合并
public class Solution {

    public ListNode mergeKLists(ListNode[] lists) {
        return merge(lists,0,lists.length-1);
    }

    private ListNode merge(ListNode[] lists,int left,int right){
        if (left==right){
            return lists[left];
        }
        if (left>right){
            return null;
        }
        int mid=(left+right)>>1;//因为lists.length<500 所以这里不用担心越界 可以使用left+right
        return mergeTwoLists(merge(lists, left, mid),merge(lists, mid+1, right));
    }

    private ListNode mergeTwoLists(ListNode a,ListNode b){
        if (a==null||b==null){
            return a!=null?a:b;
        }
        ListNode head=new ListNode(0);//设置一个头节点 返回的时候返回他的next节点
        ListNode tail=head;//记录下一个插入位置的前一个位置
        ListNode preA=a;//a链表的当前节点
        ListNode preB=b;//b链表的当前节点
        while (preA!=null&&preB!=null){
            if (preA.val<preB.val){
                tail.next=preA;
                preA=preA.next;
            }else {
                tail.next=preB;
                preB=preB.next;
            }
            tail=tail.next;
        }
        tail.next=preA!=null?preA:preB;
        return head.next;
    }
} 

5.双端队列

(1)滑动窗口最大值(239)

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

输入:nums = [1], k = 1
输出:[1]
//优先队列
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,3,-1,-3,5,3,6,7};
        int k=3;
        Solution solution=new Solution();
        int[] ints = solution.maxSlidingWindow(arr, k);
        for (int i:ints) {
            System.out.print(i+" ");
        }
    }

    public int[] maxSlidingWindow(int[] nums, int k) {
        int n= nums.length;
        int[] res=new int[n-k+1];
        //定义一个大根堆
        PriorityQueue<int[]> heap=new PriorityQueue<>(new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                if (o1[0]!=o2[0]){//如果元素值不相等 按照元素值降序排序
                    return o2[0]-o1[0];
                }
                //如果元素值相等 按照索引值降序排序
                return o2[1]-o1[1];
            }
        });
        //将数组前k个元素加入大根堆
        for (int i=0;i<k;i++){
            heap.add(new int[]{nums[i],i});
        }
        res[0]=heap.peek()[0];
        //开始滑动窗口
        for (int i=k;i<n;i++){
            heap.add(new int[]{nums[i],i});//当前元素加入大根堆
            //如果此时最大值不在窗口内 就将堆顶元素移除 直到堆顶元素最大值在当前窗口内
            //此时堆顶元素就是当前窗口内的最大值
            while (heap.peek()[1]<=i-k){
                heap.poll();//弹出堆顶元素
            }
            res[i-k+1]=heap.peek()[0];
        }
        return res;
    }
}

6.哈希表

哈希表又称为散列表,使用O(n)的空间复杂度存储数据,通过hash函数映射位置,从而实现近似O(1)的时间
复杂度的插入、删除、查找等操作

(1)两数之和(1)

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。

//暴力遍历
class Solution {

    public static void main(String[] args) {
        int[] arr={2,7,11,15};
        int k=9;
        Solution solution=new Solution();
        int[] ints = solution.twoSum(arr, k);
        for (int i:ints) {
            System.out.print(i+" ");
        }
    }
    
    public int[] twoSum(int[] nums, int target) {
        int n= nums.length;
        for (int i=0;i<n-1;i++){
            for (int j=i+1;j<n;j++){
                if (nums[j]==target-nums[i]){
                    return new int[]{i,j};
                }
            }
        }
        return new int[0];
    }
}


//使用hash表
public class Solution {

    public static void main(String[] args) {
        int[] arr={2,7,11,15};
        int k=9;
        Solution solution=new Solution();
        int[] ints = solution.twoSum(arr, k);
        for (int i:ints) {
            System.out.print(i+" ");
        }
    }

    public int[] twoSum(int[] nums, int target) {
        int n= nums.length;
        Map<Integer,Integer> map=new HashMap<>();
        for (int i=0;i<n;i++){
            //如果map里存在一个key使它和当前值相加等于target 直接返回他们的下标
            if (map.containsKey(target-nums[i])){
                return new int[]{map.get(target-nums[i]),i};
            }
            map.put(nums[i],i);
        }
        return new int[0];
    }
}

(2)最长连续序列(128)

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
public class Solution {

    public static void main(String[] args) {
        int[] arr={100,4,200,1,3,2};
        Solution solution=new Solution();
        System.out.println(solution.longestConsecutive(arr));
    }

    public int longestConsecutive(int[] nums) {
        int n= nums.length;
        if (n==0){
            return 0;
        } else if (n==1) {
            return 1;
        }
        Set<Integer> set=new HashSet<>();
        int longestLen=0;//最长序列长度
        //将数组中元素加入set集合中 并去重
        for (int i=0;i<n;i++){
            set.add(nums[i]);
        }
        //遍历数组中每一个元素
        for (int i=0;i<n;i++){
            //当一个元素在数组中不存在前驱元素时 从这个元素出发才可以得到当前最长序列
            //如果存在前驱元素 那么从前驱元素开始 得到的序列长度一定大于从当前这个元素开始的序列长度
            if (!set.contains(nums[i]-1)){
                int curLen=1;//当前长度
                int curNum=nums[i];//当前元素
                //当当前元素存在后继元素时 当前长度+1
                while (set.contains(curNum+1)){
                    curLen++;
                    curNum++;
                }
                longestLen=Math.max(longestLen,curLen);
            }
        }
        return longestLen;
    }
}

(3)直线上最多的点数

给你一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。

输入:points = [[1,1],[2,2],[3,3]]
输出:3
public class Solution {

    public static void main(String[] args) {
        int[][] arr={{1,1},{3,2},{5,3},{4,1},{2,3},{1,4}};
        Solution solution=new Solution();
        System.out.println(solution.maxPoints(arr));
    }

    public int maxPoints(int[][] points) {
        int n=points.length;
        if (n<=2){
            return n;
        }
        int maxPoint=0;//最多有多少个点在同一条直线上
        for (int i=0;i<n;i++){
            //当共线点的数量 > n/2时 或者 共线点的数量 >= 当前点i之后所剩的点的数量时 就直接退出循环
            if (maxPoint>=n-i||maxPoint>n/2){
                break;
            }
            //用map集合来存储每个不同直线上共线点的个数
            Map<Integer,Integer> map=new HashMap<>();
            for (int j=i+1;j<n;j++){
                int x=points[j][0]-points[i][0];
                int y=points[j][1]-points[i][1];
                //当x=0或者y=0时 将不为0的一方设置为1
                if (x==0){
                    y=1;
                } else if (y==0) {
                    x=1;
                }else {
                    //假设y始终为正数
                    if (y<0){
                        x=-x;
                        y=-y;
                    }
                    //求x和y的最大公倍数
                    int gcdXY=gcd(Math.abs(x),Math.abs(y));
                    //化简x和y
                    x/=gcdXY;
                    y/=gcdXY;
                }
                //将x和y用一个正数表示 作为集合的key
                int key=y+20001*x;
                if (map.containsKey(key)){//当集合中存在key时 点数+1
                    map.put(key,map.get(key)+1);
                }else {//集合中不存在key时 将这个key加入集合
                    map.put(key,1);
                }
            }
            int preMax=0;//当前i所在的最大点数
            //遍历当前的map集合 求出当前的最大值
            for (Map.Entry<Integer,Integer> entry: map.entrySet()){
                preMax=Math.max(preMax, entry.getValue()+1);
            }
            //将每一次的当前最大值与最大值做比较 求总的最大值
            maxPoint=Math.max(preMax,maxPoint);
        }
        return maxPoint;
    }

    private int gcd(int x,int y){
        return y!=0?gcd(y,x%y):x;
    }
}

7.前缀和与积分图

(1)区域和检索 - 数组不可变(303)

给定一个整数数组 nums,处理以下类型的多个查询:
计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
实现 NumArray 类:
NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )

//暴力
public class NumArray {

    int[] arr;

    public NumArray(int[] nums) {
        this.arr=nums;
    }

    public int sumRange(int left, int right) {
        int sum=0;
        for (int i=left;i<=right;i++){
            sum+=arr[i];
        }
        return sum;
    }

}


//前缀和
public class NumArray {

    int[] arr;//前缀和数组

    public NumArray(int[] nums) {
        int n=nums.length;
        arr=new int[n+1];//前缀和数组的arr[0]=0 这样可以避免i=0的特殊判断
        for (int i=0;i<n;i++){
            arr[i+1]=arr[i]+nums[i];
        }
    }

    public int sumRange(int left, int right) {
        //left~right这个区间元素的和是 0~right的前缀和减去0~left-1的前缀和的值
        return arr[right+1]-arr[left];
    }

}

(2)二维区域和检索 - 矩阵不可变(304)

给定一个二维矩阵 matrix,以下类型的多个请求:
计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。
实现 NumMatrix 类:
NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和 。

//使用一维前缀和
public class NumMatrix {

    int arr[][];

    public NumMatrix(int[][] matrix) {
        int m= matrix.length;//行
        int n=matrix[0].length;//列
        arr=new int[m][n+1];
        for (int i=0;i<m;i++){
            //计算出每一行的前缀和
            for (int j=0;j<n;j++){
                arr[i][j+1]=arr[i][j]+matrix[i][j];
            }
        }
    }

    public int sumRegion(int row1, int col1, int row2, int col2) {
        int sum=0;
        for (int i=row1;i<=row2;i++){
            sum+=arr[i][col2+1]-arr[i][col1];
        }
        return sum;
    }

}


//使用二维前缀和
public class NumMatrix {

    int arr[][];

    public NumMatrix(int[][] matrix) {
        int m= matrix.length;//行
        int n=matrix[0].length;//列
        arr=new int[m+1][n+1];
        //计算出第一行的所有列的前缀和
        for (int i=0;i<m;i++){
            for (int j=0;j<n;j++){
                arr[i+1][j+1]=arr[i][j+1]+arr[i+1][j]-arr[i][j]+matrix[i][j];
            }
        }
    }

    public int sumRegion(int row1, int col1, int row2, int col2) {
        return arr[row2+1][col2+1]-arr[row1][col2+1]-arr[row2+1][col1]+arr[row1][col1];
    }

}

(3)和为 K 的子数组(560)

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。

输入:nums = [1,1,1], k = 2
输出:2

输入:nums = [1,2,3], k = 3
输出:2
//暴力枚举
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,3};
        int k=3;
        Solution solution=new Solution();
        System.out.println(solution.subarraySum(arr,k));
    }

    public int subarraySum(int[] nums, int k) {
        int n= nums.length;
        int count=0;//连续子数组的个数
        //依次遍历每个元素 分别以每个元素结尾向前寻找 看能否找到连续子数组的和为k
        for (int i=0;i<n;i++){
            int sum=0;//连续子数组的和
            for (int j=i;j>=0;j--){
                sum+=nums[j];
                if (sum==k){
                    count++;
                }
            }
        }
        return count;
    }
}


//前缀和+哈希表优化
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,3};
        int k=3;
        Solution solution=new Solution();
        System.out.println(solution.subarraySum(arr,k));
    }

    public int subarraySum(int[] nums, int k) {
        int n= nums.length;
        int count=0;//连续子数组的个数
        int pre=0;//前缀和
        Map<Integer,Integer> map=new HashMap<>();
        map.put(pre,1);
        for (int i=0;i<n;i++){
            pre+=nums[i];
            if (map.containsKey(pre-k)){
                count+=map.get(pre-k);
            }
            map.put(pre,map.getOrDefault(pre,0)+1);
        }
        return count;
    }
}

8.练习

(1)重塑矩阵(566)

在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。

给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。

重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。

如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

输入:mat = [[1,2],[3,4]], r = 1, c = 4
输出:[[1,2,3,4]]

输入:mat = [[1,2],[3,4]], r = 2, c = 4
输出:[[1,2],[3,4]]
public class Solution {

    public static void main(String[] args) {
        int[][] arr={{1,2},{3,4}};
        int r=1;
        int c=4;
        Solution solution=new Solution();
        int[][] ints = solution.matrixReshape(arr, r, c);
        System.out.println("行:"+ints.length+"   "+"列:"+ints[0].length);
    }

    public int[][] matrixReshape(int[][] mat, int r, int c) {
        int m= mat.length;
        int n=mat[0].length;
        if (r*c!=m*n){
            return mat;
        }
        int[][] arr=new int[r][c];
        Queue<Integer> queue=new LinkedList<>();
        //将数组元素入队列
        for (int i=0;i<m;i++){
            for (int j=0;j<n;j++){
                queue.add(mat[i][j]);
            }
        }
        //将队列的元素插入新的数组
        for (int i=0;i<r;i++){
            for (int j=0;j<c;j++){
                arr[i][j]=queue.poll();
            }
        }
        return arr;
    }
}

(2)用队列实现栈(225)

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:

你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

public class MyStack {

    Queue<Integer> queue;
    Queue<Integer> auxQueue;//辅助栈

    public MyStack() {
        queue=new LinkedList<>();
        auxQueue=new LinkedList<>();
    }

    public void push(int x) {
        //首先将queue里剩余的元素弹入auxQueue中 为了保证元素顺序
        while (!queue.isEmpty()){
            auxQueue.add(queue.poll());
        }
        //将auxQueue中元素按照顺序弹入queue中
        while (!auxQueue.isEmpty()){
            queue.add(auxQueue.poll());
        }
        queue.add(x);
    }

    public int pop() {
        //首先将queue里剩余的元素弹入auxQueue中 为了保证元素顺序
        while (!queue.isEmpty()){
            auxQueue.add(queue.poll());
        }
        //在每一次操作前 先将auxQueue中的元素弹入queue中
        while (!auxQueue.isEmpty()){
            queue.add(auxQueue.poll());
        }
        while (queue.size()>1){
            auxQueue.add(queue.poll());
        }
        return queue.poll();
    }

    public int top() {
        //首先将queue里剩余的元素弹入auxQueue中 为了保证元素顺序
        while (!queue.isEmpty()){
            auxQueue.add(queue.poll());
        }
        //在每一次操作前 先将auxQueue中的元素弹入queue中
        while (!auxQueue.isEmpty()){
            queue.add(auxQueue.poll());
        }
        while (queue.size()>1){
            auxQueue.add(queue.poll());
        }
        return queue.peek();
    }

    public boolean empty() {
        while (queue.isEmpty()&&auxQueue.isEmpty()){
            return true;
        }
        return false;
    }
}

(3)下一个更大元素 II(503)

给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。

数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。

输入: nums = [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

输入: nums = [1,2,3,4,3]
输出: [2,3,4,-1,4]
//小根堆+循环数组
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,1};
        Solution solution=new Solution();
        int[] ints = solution.nextGreaterElements(arr);
        for (int i:ints){
            System.out.print(i+" ");
        }
    }

    public int[] nextGreaterElements(int[] nums) {
        int n= nums.length;
        if (n==1){
            return new int[]{-1};
        }
        int[] arr=new int[n];
        //先将数组中所有位置初始化为-1
        Arrays.fill(arr,-1);
        PriorityQueue<int[]> heap=new PriorityQueue<>(new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                if (o1[0]==o2[0]){
                    return o1[1]-o2[1];
                }
                return o1[0]-o2[0];
            }
        });
        for (int i=0;i<n*2-1;i++){
            //当小根堆不为空并且堆顶元素小于当前元素时 堆顶元素对应位置放当前元素
            while (!heap.isEmpty()&&heap.peek()[0]<nums[i%n]){
                arr[heap.poll()[1]]=nums[i%n];
            }
            //当前元素入堆
            heap.add(new int[]{nums[i%n],i%n});
        }
        return arr;
    }
}


//单调栈+循环数组
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,1};
        Solution solution=new Solution();
        int[] ints = solution.nextGreaterElements(arr);
        for (int i:ints){
            System.out.print(i+" ");
        }
    }

    public int[] nextGreaterElements(int[] nums) {
        int n= nums.length;
        if (n==1){
            return new int[]{-1};
        }
        int[] arr=new int[n];
        //先将数组中所有位置初始化为-1
        Arrays.fill(arr,-1);
        //单调栈中保存数组元素下标
        Deque<Integer> stack=new LinkedList<>();
        for (int i=0;i<n*2-1;i++){
            //当栈不为空并且当前元素大于栈顶下标所对应的元素时 将栈顶元素对应位置赋值为当前元素
            while (!stack.isEmpty()&&nums[stack.peek()]<nums[i%n]){
                arr[stack.poll()]=nums[i%n];
            }
            //当前元素下标入栈
            stack.push(i%n);
        }
        return arr;
    }
}

(4)存在重复元素(217)

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。

输入:nums = [1,2,3,1]
输出:true

输入:nums = [1,2,3,4]
输出:false

输入:nums = [1,1,1,3,3,4,3,2,4,2]
输出:true
//排序
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,1};
        Solution solution=new Solution();
        System.out.println(solution.containsDuplicate(arr));
    }

    public boolean containsDuplicate(int[] nums) {
        int n=nums.length;
        if (n==1){
            return false;
        }
        Arrays.sort(nums);
        for (int i=0;i<n-1;i++){
            if (nums[i]==nums[i+1]){
                return true;
            }
        }
        return false;
    }
}


//集合
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,1};
        Solution solution=new Solution();
        System.out.println(solution.containsDuplicate(arr));
    }

    public boolean containsDuplicate(int[] nums) {
        int n=nums.length;
        if (n==1){
            return false;
        }
        Set<Integer> set=new HashSet<>();
        for (int i=0;i<n;i++){
            //当集合中存在某个值时 证明这个值算上当前值至少出现了两次 直接返回true
            if (set.contains(nums[i])){
                return true;
            }
            set.add(nums[i]);
        }
        return false;
    }
}

(5)数组的度

给定一个非空且只包含非负数的整数数组 nums,数组的 度 的定义是指数组里任一元素出现频数的最大值。

你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

输入:nums = [1,2,2,3,1]
输出:2
解释:
输入数组的度是 2 ,因为元素 1 和 2 的出现频数最大,均为 2 。
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组 [2, 2] 的长度为 2 ,所以返回 2 。

输入:nums = [1,2,2,3,1,4,2]
输出:6
解释:
数组的度是 3 ,因为元素 2 重复出现 3 次。
所以 [2,2,3,1,4,2] 是最短子数组,因此返回 6 。
//哈希表
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,2,3,1};
        Solution solution=new Solution();
        System.out.println(solution.findShortestSubArray(arr));
    }

    public int findShortestSubArray(int[] nums) {
        int n= nums.length;
        if (n==1){
            return 1;
        }
        int maxCount=0;//出现最大的频数
        int minLen=0;//出现频数最多且子数组长度最短的长度
        //哈希表 用来存储元素 和 其出现的次数以及第一次出现和最后一次出现的位置
        Map<Integer,int[]> map=new HashMap<>();
        for (int i=0;i<n;i++){
            if (map.containsKey(nums[i])){
                //更新出现的次数已经最后一次出现的位置
                map.get(nums[i])[0]++;
                map.get(nums[i])[2]=i;
            }else {
                map.put(nums[i],new int[]{1,i,i});
            }
        }
        //依次获取每一个value 比较它们的次数以及对应的子数组的长度 得到答案
        for (Map.Entry<Integer,int[]> entry:map.entrySet()){
            int[] arr= entry.getValue();
            if (maxCount<arr[0]){
                maxCount=arr[0];//令maxCount=当前频数
                minLen=arr[2]-arr[1]+1;//更改当前最短子数组长度
            } else if (maxCount==arr[0]) {
                minLen=Math.min(minLen,arr[2]-arr[1]+1);
            }
        }
        return minLen;
    }
}

(6)最长和谐子序列(594)

和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1 。

现在,给你一个整数数组 nums ,请你在所有可能的子序列中找到最长的和谐子序列的长度。

数组的子序列是一个由数组派生出来的序列,它可以通过删除一些元素或不删除元素、且不改变其余元素的顺序而得到。

输入:nums = [1,3,2,2,5,2,3,7]
输出:5
解释:最长的和谐子序列是 [3,2,2,2,3]

输入:nums = [1,2,3,4]
输出:2

输入:nums = [1,1,1,1]
输出:0
//排序
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,3,4};
        Solution solution=new Solution();
        System.out.println(solution.findLHS(arr));
    }

    public int findLHS(int[] nums) {
        int n= nums.length;
        if (n<2){
            return 0;
        }
        int maxLen=0;//最长和谐子序列的长度
        Arrays.sort(nums);
        for (int i=0;i<n-1;i++){
            int j=i;
            int preMin=nums[i];//当前和谐子序列最小值
            //当当前子序列满足和谐子序列时
            while (j<(n-1)&&(preMin+1)>=nums[j+1]){
                j++;
            }
            //当当前最小值不等于最大值时 才成立
            if (preMin!=nums[j]){
                maxLen=Math.max(maxLen,j-i+1);
            }
        }
        return maxLen;
    }

}


//哈希表
public class Solution {

    public static void main(String[] args) {
        int[] arr={1,2,3,4};
        Solution solution=new Solution();
        System.out.println(solution.findLHS(arr));
    }

    public int findLHS(int[] nums) {
        int n= nums.length;
        if (n<2){
            return 0;
        }
        int maxLen=0;//最长和谐子序列的长度
        Map<Integer,Integer> map=new HashMap<>();
        //遍历数组 得到每个元素出现的次数
        for (int i=0;i<n;i++){
            if (map.containsKey(nums[i])){
                map.put(nums[i],map.get(nums[i])+1);
            }else {
                map.put(nums[i],1);
            }
        }
        //遍历map 对每一个key值 如果找到key+1也在map里 那么它们对应的和谐子序列长度就是它们的value值相加
        for (int key:map.keySet()){
            if (map.containsKey(key+1)){
                maxLen=Math.max(maxLen,map.get(key)+map.get(key+1));
            }
        }
        return maxLen;
    }

}

(7)寻找重复数(287)

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

输入:nums = [1,3,4,2,2]
输出:2

输入:nums = [3,1,3,4,2]
输出:3
public class Solution {

    public static void main(String[] args) {
        int[] arr={3,1,3,4,2};
        Solution solution=new Solution();
        System.out.println(solution.findDuplicate(arr));
    }

    public int findDuplicate(int[] nums) {
        int n= nums.length;
        int repeatNum=0;//重复的整数
        int left=1;
        int right=n-1;
        while (left<=right){
            int mid=(left+right)>>1;
            int count=0;//计数 来判断重复的数出现在哪
            for (int i=0;i<n;i++){
                if (nums[i]<=mid){
                    count++;
                }
            }
            if (count<=mid){
                left=mid+1;
            }else {
                right=mid-1;
                repeatNum=mid;
            }
        }
        return repeatNum;
    }

}

(8)优势洗牌(870)

给定两个大小相等的数组 nums1 和 nums2,nums1 相对于 nums2 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。
返回 nums1 的任意排列,使其相对于 nums2 的优势最大化。

输入:nums1 = [2,7,11,15], nums2 = [1,10,4,11]
输出:[2,11,7,15]

输入:nums1 = [12,24,8,32], nums2 = [13,25,32,11]
输出:[24,32,8,12]
    public int[] advantageCount(int[] nums1, int[] nums2) {
        int n= nums1.length;
        int[] arr=new int[n];
        Integer[] a=new Integer[n];
        Integer[] b=new Integer[n];
        int left=0;//数组当前左边界
        int right=n-1;//数组当前有边界
        for (int i=0;i<n;i++){
            a[i]=i;
            b[i]=i;
        }
        //a b 分别来存储 nums1 nums2 排序后的下标值
        Arrays.sort(a,(i,j)->nums1[i]-nums1[j]);
        Arrays.sort(b,(i,j)->nums2[i]-nums2[j]);
        for (int i=0;i<n;i++){
            //当nums1的当前最小值大于nums2的当前最小值时
            if (nums1[a[i]]>nums2[b[left]]){
                arr[b[left]]=nums1[a[i]];
                left++;
            }else {
                arr[b[right]]=nums1[a[i]];
                right--;
            }
        }
        return arr;
    }

相关文章:

  • 1个月能学好网站开发吗/抖音推广方案
  • 网站建设后备案多少钱/公众号软文范例100
  • wordpress微信登陆/百度推广注册
  • 上孩做网站/搜索引擎调词工具哪个好
  • wordpress编辑导航/旺道seo软件
  • 插画网站/免费网站免费
  • 五、settings 文件
  • Java--StringBuffer 和 StringBuilder 类
  • Java基础语法
  • 【年度总结】2022回首瞻望 | 2023大展宏“兔“
  • Vue技术教程(2023-1-15)
  • Go语言运算符
  • [拆轮子] PaddleDetection中__shared__、__inject__ 和 from_config 三者分别做了什么
  • 2022尚硅谷SSM框架跟学(五)Spring基础二
  • Springboot打成JAR包后读取配置文件
  • BetaFlight飞控AOCODARC-F7MINI固件编译
  • Go语言数据结构
  • 营销科学年度复盘|9个数字,见证“科学增长”的力量