1. 程式人生 > 其它 >求元素左右小於本身的值

求元素左右小於本身的值

求元素左右小於本身的值

問題重述:

給你一個整數陣列 arr,求得每個元素左右小於其本身且距離它最近的元素,若不存在給定-1,返回所有位置的對應資訊

示例 1:

輸入:arr = [3, 4, 1, 3, 5, 2, 7]
輸出:
-1 2
0 2
-1 -1
2 5
3 5
2 -1
5 -1
輸入: repeatArr =  {3, 4, 1, 5, 3, 5, 2, 7}
輸出:
-1 2
0 2
-1 -1
2 4
2 6
4 6
2 -1
6 -1

問題分析:

輸入陣列有兩種可能,一種是不重複的陣列,一種是重複的陣列,我們只需要考慮重複陣列即可,因為不重複的陣列可以理解為是一種特殊的重複陣列。為了便於理解單調棧的使用,我將這兩種情況分別定義了一個方法。

對於不重複陣列而言,我們可以使用一個棧,在每次新增元素進棧時將當前元素和棧頂元素進行比較,如果當前元素進棧破壞了棧的單調性,則彈出棧頂元素,重複進行比較,直到棧內元素滿足單調性。我們彈出棧頂元素時,棧頂元素的下方元素就是他左邊離他最近的比它小的元素,當前想要進棧的元素是右邊離他最近的比他小的元素。(因為棧滿足單調性,所以棧頂元素的下一個元素必定小於棧頂元素,又根據棧的先入後出特性,這個元素是距離棧頂元素最近的元素,將要壓入的元素因為破壞了當前棧的單調性,也就是說,當前要加入的元素小於棧頂元素,因為是按照順序從左到右進行遍歷,所以該元素是距離棧頂元素最近的右邊元素),遍歷完陣列所有元素後,將棧內剩餘元素在進行彈出。

對於重複陣列而言,原理與不重複陣列基本相同,只是我們將所有重複的元素放在了一個列表內在存入陣列。在彈出棧頂元素時,對列表進行遍歷,列表每一個元素都進行判斷。

解題方法:

單調棧

解題:

程式碼:
package cn.basic.algorithm;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author FOLDN
 * 單調棧結構
 */
public class DullStack {
    public static void main(String[] args) {
        int[] noRepeatArr = {3, 4, 1, 3, 5, 2, 7};
        int[] repeatArr = {3, 4, 1, 5, 3, 5, 2, 7};
        int[][] noRepeatNearLess = getNoRepeatNearLess(noRepeatArr);
        int[][] repeatNearLess = getRepeatNearLess(repeatArr);
        for (int i = 0; i < noRepeatNearLess.length; i++) {
            System.out.println(noRepeatNearLess[i][0] + " " + noRepeatNearLess[i][1]);
        }
        System.out.println("----------------");
        for (int i = 0; i < repeatNearLess.length; i++) {
            System.out.println(repeatNearLess[i][0] + " " + repeatNearLess[i][1]);
        }
    }

    public static int[][] getNoRepeatNearLess(int[] arr) {
        // 建立一個二維陣列存放結果
        int[][] result = new int[arr.length][2];
        // 建立一個棧
        Stack<Integer> stack = new Stack<>();
        // 對陣列arr進行迴圈
        for (int i = 0; i < arr.length; i++) {
            // 當單調棧不為空且棧頂元素大於當前元素時,將當前棧頂元素元素移除(如果用小於號那麼就得到的時最大元素)
            while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
                // 獲得棧頂元素,這個元素是陣列下標
                int index = stack.pop();
                // 此時判斷棧是否為空,若為空的話,沒有比它更小的值,那麼左邊加入-1
                if (stack.isEmpty()) {
                    result[index] = new int[]{-1, i};
                } else {
                    // 不為空則棧頂下面的下標對應的值就是左邊的最小值對應的下標,當前i為右邊最小值對應下標
                    result[index] = new int[]{stack.peek(), i};
                }
            }
            // 將當前下標加入棧
            stack.push(i);
        }
        // 剩餘的元素都只有左邊有最小值,右邊都沒有最小值,右邊直接加入-1,原理同上
        while (!stack.isEmpty()) {
            int index = stack.pop();
            if (stack.isEmpty()) {
                result[index] = new int[]{-1, -1};
            } else {
                result[index] = new int[]{stack.peek(), -1};
            }
        }
        return result;
    }
    public static int[][] getRepeatNearLess(int[] arr) {
        // 建立一個二維陣列存放結果
        int[][] result = new int[arr.length][2];
        // 建立一個棧,其中存放列表,列表中存放所有值相同的下標
        Stack<List<Integer>> stack = new Stack<>();
        // 開始迴圈
        for (int i = 0; i < arr.length; i++) {
            // 進行排序,使得棧滿足單調性(這裡的單調性是指每一個列表值對應的陣列值之間滿足單調性)
            while(!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]){
                List<Integer> pop = stack.pop();
                // 對列表中的值進行遍歷,當前的i為右邊最小值,該列表下方列表中最後一個元素為左邊最小值(因為壓入順序越後面的越近,如果改成選擇最遠的那麼就取第一個元素)
                for (Integer index : pop) {
                    if (stack.isEmpty()){
                        // 當該列表下面沒有元素時,代表當前值左邊沒有比它更小的值
                        result[index] = new int[]{-1,i};
                    }else {
                        result[index] = new int[]{stack.peek().get(stack.peek().size()-1),i};
                    }
                }
            }
            if (!stack.isEmpty() && arr[stack.peek().get(stack.peek().size()-1)] == arr[i]){
                // 當前值和列表中對應的陣列值相同,將當前的i加入列表中
                stack.peek().add(i);
            } else {
                ArrayList<Integer> newList = new ArrayList<>();
                newList.add(i);
                stack.push(newList);
            }
        }
        while (!stack.isEmpty()){
            // 原理同上,只是右邊的值只能是-1,因為篩選到了最後,每個值右邊都沒有比他更大的值了
            List<Integer> pop = stack.pop();
            for (Integer index : pop) {
                if (stack.isEmpty()){
                    result[index] = new int[]{-1,-1};
                }else {
                    result[index] = new int[]{stack.peek().get(stack.peek().size()-1),-1};
                }
            }
        }
        return result;
    }
}

程式碼解析:

重複陣列與不重複陣列原理基本一致,這裡只講述重複陣列

建立一個存放列表的棧,用來存放陣列下標。對陣列元素進行迴圈,每一個元素迴圈之前,先進行條件判斷,只有不影響棧的單調性的元素才能加入棧,如果破壞了棧的單調性,那就彈出棧頂元素,彈出棧頂元素時,要對棧頂元素(列表)進行遍歷,對其中每一個值進行條件判斷,然後得出對應的左右資訊,若棧為空,左端則沒有比其更小的元素,左邊為-1,右邊為當前要加入的下標,若棧不為空,棧頂元素的下一個元素(列表)的最後一個值就是棧頂列表中當前值的左邊最近值(因為棧是先入後出),右邊同樣為當前要加入的下標,直到滿足條件再加入。加入棧中時要進行判斷,判斷當前棧頂列表中的元素對應的陣列值和當前陣列值是否相同,若相同,則把當前下標加入棧頂列表中,否則建立一個新的列表加入棧中。最後彈出棧中剩餘的元素。

總結:

單調棧:棧內元素按照規則排序,如果壓入的元素不符合規則,則彈出隊尾元素,直到滿足規則。(只能操作棧頂)