zoukankan      html  css  js  c++  java
  • OptimalSolution(5)--数组和矩阵问题(1)简单

      一、转圈打印矩阵

      题目:给定一个整型矩阵matrix,按照转圈的方式打印它。

      要求:额外空间复杂度为O(1)

    1   2   3   4
    5   6   7   8
    9   10  11  12
    13  14  15  16
    打印结果为:1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10

      思路:矩阵分圈处理问题。用矩阵中左上角的坐标(tR,rC)和右下角的坐标(dR,dC)就可以表示一个子矩阵。

      例如:(0,0)和(3,3)表示的是原来矩阵的最外层,此时打印:1 2 3 4 8 12 16 15 14 13 9 5

         然后令tR和tC加1,dR和dC减1,即(1,1)和(2,2)表示原矩阵的内层,此时打印:6 7 11 10

            然后令tR和tC加1,得到(2,2),而dR和dC减1,得到(1,1),此时左上角坐标跑到了右下角坐标的右方或下方,整个过程停止

        public void printEdge(int[][] m, int tR, int tC, int dR, int dC) {
            if (tR == dR) {            // (tR,tC)和(dR,dC)在同一行上
                for(int i = tC; i <= dC; i++) {
                    System.out.print(m[tR][i] + " ");
                }
            } else if (tC == dC) {    // (tR,tC)和(dR,dC)在同一列上
                for(int i = tR; i <= dR; i++) {
                    System.out.print(m[i][tC] + " ");
                }
            } else {                // (tR,tC)在左上,(dR,dC)在右下
                int curC = tC;
                int curR = tR;
                while (curC != dC) {    // 先打印最上面一行
                    System.out.print(m[tR][curC] + " ");
                    curC++;
                }
                while (curR != dR) {    // 先打印最右面一行
                    System.out.print(m[curR][dC] + " ");
                    curR++;
                }
                while (curC != tC) {    // 先打印最下面一行
                    System.out.print(m[dR][curC] + " ");
                    curC--;
                }
                while (curR != tR) {    // 先打印最左面一行
                    System.out.print(m[curR][tC] + " ");
                    curR--;
                }
            }
        }
        
        public void spiralOrderPrint(int[][] matrix) {
            int tR = 0;
            int tC = 0;
            int dR = matrix.length - 1;
            int dC = matrix[0].length - 1;
            while (tR <= dR && tC <= dC) {
                printEdge(matrix, tR++, tC++, dR--, dC--);
            }
        }
        
        public static void main(String[] args) {
            int[][] matrix= {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
            new e1().spiralOrderPrint(matrix);
        }
    转圈打印矩阵

      二、将正方形矩阵顺时针转动90°

      题目:给定一个N×N的矩阵matrix,把这个矩阵调整成顺时针90°后的形式

    1  2  3  4                  13 9  5 1
    5  6  7  8                  14 10 6 2
    9  10 11 12                 15 11 7 3
    13 14 15 16      →          16 12 8 4

      要求:额外空间复杂度为O(1)

      思路:仍然采用分圈的调整方式。

      例如:(0,0)和(3,3)时,让1,4,16,13为一组,让1占据4的位置,4占据16的位置,16占据13的位置,13占据1的位置。然后2,8,15,9和3,12,14,5同理

            (1,1)和(2,2)时,直接调整6 7 10 11即可

            如果子矩阵的大小为M×M,那么一共就有M-1组需要调整

    package Chapter8;
    
    public class e2 {
    
        public void rorateEdge(int[][] m, int tR, int tC, int dR, int dC) {
            int times = dR - tR;    // 需要调整的组数
            int tmp = 0;
            for (int i = 0; i != times; i++) {
                tmp = m[tR][tC + i];             
                m[tR][tC + i] = m[dR - i][tC];
                m[dR - i][tC] = m[dR][dC - i];
                m[dR][dC - i] = m[tR + i][dC];
                m[tR + i][dC] = tmp;
            }
        }
        
        public void rorate(int[][] matrix) {
            int tR = 0;
            int tC = 0;
            int dR = matrix.length - 1;
            int dC = matrix[0].length - 1;
            while (tR < dR) {
                rorateEdge(matrix, tR++, tC++, dR--, dC--);
            }
        }
        
        public static void main(String[] args) {
            int[][] matrix= {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
            new e2().rorate(matrix);
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix[0].length; j++) {
                    System.out.print(matrix[i][j] + " ");
                }
                System.out.println();
            }
        }
    }
    将正方形矩阵顺时针旋转90°

      三、“之”字形打印矩阵

      题目:给定一个矩阵matrix,按照“之”字形的方式打印矩阵

    1 2 3 4
    5 6 7 8
    9 10 11 12    打印:1 2 5 9 6 3 4 7 10 11 8 12

      解法:

      第一步:上坐标(tR,tC)初始为(0,0),先沿着矩阵第一行移动(tC++),当到达第一行最右边的元素后,再沿着矩阵最后一列移动(tR++)

      第二步:下坐标(dR,dC)初始为(0,0),先沿着矩阵第一列移动(dR++),当到达第一列最下边的元素时,再沿着矩阵最后一行移动(dC++)

      第三步:上坐标与下坐标同步移动,每次移动后的上坐标与下坐标的连线就是矩阵中的一条斜线,打印斜线上的元素即可

      第四步:如果上次斜线是从左下到右上打印的,这次一定是从右上向左下打印的,反之亦然。把打印的方向用boolean表示,每次取反即可。

      例如:第一条斜线:1,第二条斜线:2 5,第三条斜线:3 6 9,第四条斜线:4 7 10,第五条斜线:8 11,第六条斜线:12

    package Chapter8;
    
    public class e3 {
    
        public void printLevel(int[][] m, int tR, int tC, int dR, int dC, boolean f) {
            if (f) {
                while (tR != dR + 1) {
                    System.out.print(m[tR++][tC--] + " ");
                }
            } else {
                while (dR != tR - 1) {
                    System.out.print(m[dR--][dC++] + " ");
                }
            }
        }
        
        public void printMatrixZigZag(int[][] matrix) {
            int tR = 0, tC = 0, dR = 0, dC = 0;
            int endR = matrix.length - 1;
            int endC = matrix[0].length - 1;
            boolean fromUp = false;
            while (tR != endR + 1) {
                printLevel(matrix, tR, tC, dR, dC, fromUp);
                tR = tC == endC ? tR + 1 : tR;    // 没走到最右边之前,往右走
                tC = tC == endC ? tC : tC + 1;    // 走到了最右边,往下走
                dC = dR == endR ? dC + 1 : dC;    // 没走到最下边之前,往下走
                dR = dR == endR ? dR : dR + 1;    // 走到了最下边,往右走
                fromUp = !fromUp;                // 反转打印方向
            }
            System.out.println();
        }
    }
    “之”字形打印矩阵

      四、需要排序的最短子数组长度

      题目:给定一个无序数组arr,求出需要排序的最短子数组长度

      例如:arr=[1,5,3,4,2,6,7],只有[[5,3,4,2]需要排序,因此返回4

      解法:O(N) + O(1)

      第一步:初始化变量noMinIndex= -1,从右向左遍历,记录右侧出现过的数的最小值,记为min。同时,如果arr[i]>min,说明如果要整体有序,min值必定会挪到arr[i]的左边。用noMinIndex记录最左边出现这种情况的位置。如果遍历完成noMinIndex仍然等于-1,说明不需要排序。

      第二步:初始化变量noMaxIndex=-1,从左向右遍历,同理,记录左侧出现过的最大值,同时记录出现最后arr[i]<max的位置。

      第三步:返回arr[noMinIndex...noMaxIndex]的长度即可

      例如:从右到左,遍历到4>min=2,遍历到3>min=2,遍历到5>min=2,遍历到1,因此最后出现>min的位置就是5所在的位置

        public int getMinLength(int[] arr) {
            if (arr == null || arr.length == 0) {
                return 0;
            }
            int min = arr[arr.length - 1];
            int noMinIndex = -1;
            for (int i = arr.length - 2; i >= 0; i--) {
                if (arr[i] > min) {
                     noMinIndex = i;
                } else {
                    min = Math.min(min, arr[i]);
                }
            }
            if (noMinIndex == -1) {
                return 0;
            }
            int max = arr[0];
            int noMaxIndex = -1;
            for (int i = 1; i < arr.length; i++) {
                if (arr[i] < max) {
                    noMaxIndex = i;
                } else {
                    max = Math.max(max, arr[i]);
                }
            }
            return noMaxIndex - noMinIndex + 1;
        }
    需要排序的最短子数组

      五、在行列都排好序的矩阵中找数

      题目:给定一个行列都还排好序的N×M的整型矩阵matrix和一个整数K,判断K是否在matrix中

    0 1 2 5
    2 3 4 7
    4 4 4 8
    5 7 7 9 如果K为7,返回false;如果K为6,返回false

      要求:时间复杂度为O(N+M),额外空间复杂度为O(1)

      方法1:从矩阵的最右上角开始寻找row=0,col=M-1,然后比较当前matrix[row][col]与K的关系

        1.如果与K相等,直接返回true

        2.如果比K大,由于列已经排好序,说明下方的数都比K大,令col--,重复2

        3.如果比K小,由于行已经排好序,说明左方的数都比K小,令row++,重复2

      方法2:从矩阵的最左下角开始寻找row=N-1,col=0,然后比较当前matrix[row][col]与K的关系

        1.如果与K相等,直接返回true

        2.如果比K大,由于行已经排好序,说明右方的数都比K大,令row--,重复2

        3.如果比K小,由于列已经排好序,说明上方的数都比K小,令col++,重复2

        public boolean isContains(int[][] matrix, int K) {
            int row = 0;
            int col = matrix[0].length - 1;
            while (row < matrix.length && col > -1) {
                if (matrix[row][col] == K) {
                    return true;
                } else if (matrix[row][col] > K) {
                    col--;
                } else {
                    row++;
                }
            }
            return false;
        }
    在行列都排好序的矩阵中找数

      六、自然数数组的排序

      题目:给定一个长度为N的整型数组arr,其中有N个互不相等的自然数N,实现排序,即arr[index]=index+1

      要求:O(N) + O(1)

      方法1:从左到右遍历arr,

        1.如果arr[i]=i+1,说明不需要调整

        2.如果arr[i] != i+1,则需要进行调整。

          方式1:跳跃操作。例如:[1,2,5,3,4],arr[2]=5,把5放在arr[4]有[1,2,5,3,5],把4放在arr[3]有[1,2,5,4,5],把3放在arr[2]上有[1,2,3,4,5],即回到了原位置arr[2]

        public void sort1(int[] arr) {
            int tmp = 0;
            int next = 0;
            for (int i = 0; i < arr.length; i++) {
                tmp = arr[i];
                while (arr[i] != i + 1) {
                    next = arr[tmp - 1];
                    arr[tmp - 1] = tmp;
                    tmp = next;
                }
            }
        }
    自然数组的排序,跳跃法

          方式2:交换操作。例如:[1,2,5,3,4],arr[2]=5,5和4交换有[1,2,4,3,5],arr[2]=4,4和3交换有[1,2,3,4,5],arr[2]==3,遍历下一个位置

        public void sort2(int[] arr) {
            int tmp = 0;
            for (int i = 0; i < arr.length; i++) {
                while (arr[i] != i + 1) {
                    tmp = arr[arr[i] - 1];
                    arr[arr[i] - 1] = arr[i];
                    arr[i] = tmp;
                }
            }
        }
    自然数组的排序,交换法

      七、奇数下标都是奇数或者偶数下标都是偶数

      问题:给定一个长度不小于2的数组arr,调整arr使得要么让所有的偶数下标都是偶数,要么让所有的奇数下标都是奇数

      要求:O(N) + O(1)  

      解法:

      第一步:设置变量even,表示目前arr最左边的偶数下标,初始时even=0

      第二步:设置变量odd,表示目前arr最左边奇数下标,初始时odd=1

      第三步:不断检查arr的最后一个数,即arr[N-1],如果arr[N-1]是偶数,交换arr[N-1]和arr[even],然后令even=even+2,如果arr[N-1]是奇数,交换arr[N-1]和arr[odd],然后令odd=odd+2

      第四步:如果even或odd大于N,返回。

    以[1.8,3,2,4,6]为例,even=0,odd=1,
    end=6,6和1交换:6 8 3 2 4 1,even=2
    end=1,1和8交换:6 1 3 2 4 8,odd=3
    end=8,8和3交换:6 1 8 2 4 3,even=4
    end=3,3和2交换:6 1 8 3 4 2,odd=5
    end=2,2和4交换:6 1 8 3 2 4,even=6
    此时even大于等于长度,说明偶数下标已经都是偶数。返回

      实质上也就是:如果最后一位是偶数,就将最后一位填到偶数下标,如果最后一位是奇数,就将最后一位填到奇数下标;最后一位相当于身份识别与发送器。

        public void swap(int[] arr, int index1, int index2) {
            int tmp = arr[index1];
            arr[index1] = arr[index2];
            arr[index2] = tmp;
        }
        
        public void modify(int[] arr) {
            if (arr == null || arr.length == 0) {
                return;
            }
            int even = 0;
            int odd = 1;
            int end = arr.length - 1;
            while (even <= end && odd <= end) {
                if ((arr[end] & 1) == 0) {
                    swap(arr, end, even);
                    even += 2;
                } else {
                    swap(arr, end, odd);
                    odd += 2;
                }
            }
        }
    奇数下标都是奇数或者偶数下标都是偶数

      八、子数组的最大累加和问题

      题目:给定一个数组arr,返回子数组的最大累加和

      例如:arr=[1-2,3,5,-2,6,-1],所有的子数组中,[3,5,-2,6]可以累加出最大的和12,返回12

      要求:时间复杂度O(N),空间复杂度O(1)

      解法:(1)如果arr中没有正数,那么最大累加和一定是数组中的最大值。(2)如果arr中有正数,从左到右遍历arr,用变量cur记录每一步的累加和,当cur<0时,令cur=0,表示从下一个树开始累加;当cur>=0时,每一次累加和都可能是最大的累加和,用变量max记录cur的最大值即可。

    arr=[1-2,3,5,-2,6,-1],cur=0,max=0
    cur=1,max=1
    cur=-1,说明[1,-2]肯定不是产生最大累加和的子数组的左边部分,cur=0
    cur=3,max=3
    cur=8,max=8
    cur=6,max=8
    cur=12,max=12
    cur=11,max=1

      代码实现:

        public int maxSum(int[] arr) {
            if (arr == null || arr.length == 0) {
                return 0;
            }
            int max = Integer.MIN_VALUE;
            int cur = 0;
            for (int i = 0; i < arr.length; i++) {
                cur += arr[i];
                max = Math.max(max, cur);
                cur = cur < 0 ? 0 : cur;
            }
            return max;
        }
    子数组的最大累加和

      九、不包含本位置值的累乘数组

      题目:给定一个整型数组arr,返回不包含本位置值的累成数组

      例如:arr=[2,3,1,4],返回[12,8,24,6]。

      题目1:时间复杂度为O(N),除需要返回的结果数组外,额外空间复杂度为O(1)

      解法:结果数组记为res,所有非零数的乘积记为all,数组中0的数量记为count。如果数组中不含0,则res[i]=all/arr[i]即可;如果数组中有一个0,则res[i]=all,其他位置上都是0;如果数组中0的数量大于1,那么res所有位置上都是0。

        public int[] product1(int[] arr) {
            if (arr == null || arr.length == 0) {
                return null;
            }
            int count = 0;
            int all = 1;
            for (int i = 0; i < arr.length; i++) {
                if (arr[i] != 0) {
                    all *= arr[i];
                } else {
                    count++;
                }
            }
            int[] res = new int[arr.length];
            if (count == 0) {
                for (int i = 0; i < res.length; i++) {
                    res[i] = all / arr[i];
                }
            }
            if (count == 1) {
                for (int i = 0; i < res.length; i++) {
                    if (arr[i] == 0) {
                        res[i] = all;
                    }
                }
            }
            return res;
        }
    可以使用除法的解法

      题目2:对时间和空间复杂度的要求不变,且不可以使用除法

      解法:

        1.生成两个长度和arr一样的数组lr[]和rl[],lr[]表示从左到右的累乘,即lr[i]=arr[0...i]的累乘,rl[]表示从右到左的累乘,即rl[i]=arr[i...N-1]的累乘

        2.一个位置上除去自己的累乘,就是自己左边的累乘再乘以自己右边的累乘,即res[i]=lr[i-1]*rl[i+1]。

        3.最左边位置上res[0]=rl[1],最右边位置上res[N-1]=lr[N-2]

        4.为了避免使用两个额外的数组,可以先把res数组作为辅助计算的数组,然后把res调整成结果数组返回。具体操作是,res表示lr[],tmp表示lr[i]

    例如:arr=[2,3,1,4],经过第一步累乘后res=[2,6,6,24],tmp=1
    i=3时,res[3]=res[2]*1= 6,tmp=1*arr[3]=4
    i=2时,res[2]=res[1]*4=24,tmp = 4*arr[2]=4
    i=1时,res[1]=res[0]*4=8,tmp=4*arr[1]=12
    i=0时,res[0]=12

      代码实现:

        public int[] product2(int[] arr) {
            if (arr == null || arr.length == 0) {
                return null;
            }
            int[] res = new int[arr.length];
            int tmp = 1;
            for (int i = res.length - 1; i > 0; i--) {
                res[i] = res[i - 1] * tmp;
                tmp *= arr[i];
            }
            res[0] = tmp;
            return res;
        }
    不可以使用除法的解法

      十、数组的partition调整

      题目1:给定一个有序数组arr,调整arr使得数组的左半部分没有重复元素且升序,例如,arr=[1,2,2,2,3,3,4,5,6,6,7,7,8,8,8,9],调整后arr=[1,2,3,4,5,6,7,8,9...]。要求时间复杂度为O(N),空间复杂度为O(1)

      解法:

      1.生成变量u,在arr[0...u]上都是无重复且升序的。初始时u=0,这个区域记为A

      2.生成变量i,利用i做从左到右的遍历,在arr[u+1...i]上是不保证没有重复元素且升序的区域,初始时i=1,这个区域记为B

      3.如果arr[i] != arr[u],说明arr[i]应该加入到A区域里,所有交换arr[u+1]和arr[i],同时u++;如果arr[i]==arr[u],说明arr[i]的值之前已经加入到A区域,此时不用再加入。

    以arr=[1,2,2,2,3,3,4,5,6,6,7,7,8,8,8,9]为例,u=0,i=1,A:0...0, B:1...1
    i=1,arr[1]=2不等于arr[0]=1,于是交换arr[1]和arr[1],u=1
    i=2,arr[2]=2等于arr[1],
    i=3,arr[3]=arr[1]
    i=4,arr[4]=3不等于arr[u],于是交换3和2,u=2
    i=5,arr[5]=3等于arr[u]
    i=6,aarr[6]=4不等于arr[u],于是交换4和2...

      实质上就是双指针操作,u作为左指针,i作为右指针,如果arr[i]不等于arr[u],就令u右移一位,由于是排好序的,因此像2 2 2 3 3 4这种情况,当交换完成后,即使变成2 3 2 2 3 4时,由于i已经右移了,所以3和u所指的3也会不相等,也就是3后面不会再出现2了。

        public void leftUnique(int[] arr) {
            if (arr == null || arr.length < 2) {
                return;
            }
            int u = 0;
            int i = 1;
            while (i != arr.length) {
                if (arr[i++] != arr[u]) {
                    swap(arr, ++u, i - 1);
                }
            }
        }
    
        private void swap(int[] arr, int i, int j) {
            int tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }

      题目2:给定一个数组arr,其中只可能含有0(红球),1(蓝球),2(黄球)三个值,实现arr的排序。求时间复杂度为O(N),空间复杂度为O(1)

      解法:

        1.生成变量left,含义是arr[0...left]上都是0,初始时left=-1

        2.生成变量index,含义是arr[left+1...index]上都是1,初始时index为0

        3.生成变量right,含义是arr[right...N-1]上都是2,初始时right为N

        4.index表示遍历到arr的一个位置

          (1)如果arr[index]=1,这个值本来就应该在中区,直接index++

          (2)如果arr[index]=0,把arr[index]和arr[left+1]交换,同时扩大左区,left++。此时由于arr[left+1]一定等于1,所以直接index++即可。

          (3)如果arr[index]=2,把arr[index]和arr[right-1]交换,同时扩大右区,right--。如果原arr[right-1]=1,那么应该到位了,如果原arr[right-1]=0,那么还需要把0          调整到左区,因此综合考虑,index不变继续下一轮的判断比较好。

        5.当index==right时,说明中区和右区成功对接,三个区域划分完成。

        private void swap(int[] arr, int i, int j) {
            int tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }
        
        public void sort(int[] arr) {
            if (arr == null || arr.length < 2) {
                return;
            }
            int left = -1;
            int index = 0;
            int right = arr.length;
            while (index < right) {
                if (arr[index] == 0) {
                    swap(arr, ++left, index++);
                } else if (arr[index] == 2) {
                    swap(arr, index, --right);
                } else {
                    index++;
                }
            }
        }

      分析执行过程:

     L                             R
     ↓                             ↓
       1 2 0 0 2 2 1 1 2 1 2 0 0 0 
       ↑
    
     ↓                           ↓
       1 0 0 0 2 2 1 1 2 1 2 0 0 2 
         ↑
         
       ↓                         ↓
       0 1 0 0 2 2 1 1 2 1 2 0 0 2 
         ↑
    
         ↓                       ↓
       0 0 1 0 2 2 1 1 2 1 2 0 0 2
           ↓                     ↓
       0 0 0 1 2 2 1 1 2 1 2 0 0 2
           ↓                   ↓
       0 0 0 1 0 2 1 1 2 1 2 0 2 2
             ↓                 ↓
       0 0 0 0 1 2 1 1 2 1 2 0 2 2

             ↓               ↓
       0 0 0 0 1 0 1 1 2 1 2 2 2 2
               ↓             ↓
       0 0 0 0 0 1 1 1 2 1 2 2 2 2
               ↓             ↓
       0 0 0 0 0 1 1 1 2 1 2 2 2 2

               ↓           ↓
       0 0 0 0 0 1 1 1 2 1 2 2 2 2

               ↓         ↓
       0 0 0 0 0 1 1 1 1 2 2 2 2 2
     
     
     
     
     
     
     
    
    
  • 相关阅读:
    C# Linq 交集、并集、差集、去重
    SpringICO和DI区别
    postman调用webapi错误记录
    NetCore实例提供的依赖注入的生命周期
    ios处理暴力输出问题
    一块国外开源的视频播发器
    一个有创意的3D APP
    Flurry Analytics最近免费添加了获取新用户分析和app崩溃报告的功能
    那些域名服务商
    Moneybookers的优点
  • 原文地址:https://www.cnblogs.com/BigJunOba/p/9646370.html
Copyright © 2011-2022 走看看