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
 
 
 
 
 
 
 

 

posted @ 2018-09-14 14:26  BigJunOba  阅读(286)  评论(0编辑  收藏  举报