『剣指offer』Java実現——毎日9題——1日目


面接問題1付与演算子関数
この問題はc++言語のポインタ特性に関し,java言語はしばらく解決策を考え出せなかった.
面接問題2 Singletonモードを実現
クラスを設計します.クラスのインスタンスは1つしか生成できません.
インプリメンテーションコード
 public class LazyLoadedSingleton{
        private LazyLoadedSingleton{} //private    ,         
        private static class LazyHolder{  //       
            private static final LazyLoadedSingleton instance=new LazyLoadedSingleton();  //  final  
        }
        public static LazyLoadedSingleton getInstance(){
            return LazyHolder.instance;
        }

    }

アルゴリズムの説明
上記の方法では、アプリケーションのパフォーマンスを向上させるために遅延ロードが使用される.同時に、静的プライベート内部クラスを使用して、スレッドが安全になります.詳細については、ブログ「Java設計モードのSingleton-4つの異なる単一モード(Singleton)」を参照してください.パフォーマンスとマルチスレッドセキュリティの要因を考慮した4つの単一モードを簡単に複雑に紹介しています.
面接問題3配列で重複する配列
長さnの配列のすべての数字は0~n-1の範囲にある.配列の中にはいくつかの数字が重複していますが、いくつかの数字が重複しているのか、数字ごとに何回重複しているのか分かりません.配列のいずれかの重複する数字を見つけてください.例えば、長さ7の配列{2,3,1,0,2,5,3}を入力すると、対応する出力は重複する数字2または3である.
テストケース
  • の長さnの配列には、1つ以上の重複する数字
  • が含まれる.
  • 配列には重複する数字
  • は含まれていない.
  • 無効入力の試験例(配列は空、数字は0~n-1の範囲内ではない)
  • インプリメンテーションコード
     /**
         *       n           0~n-1    。           ,
         *             ,             。               
         * 。  ,       7   {2,3,1,0,2,5,3},             2  3。
         * @param array      
         * @return      -1(  )
         */
        public static int duplicate(int[] array){
            //      
            if (array == null || array.length <= 0 )
                return -1;
            for (int i=0;iif (array[i] < 0 || array[i]>array.length -1)
                    return -2;
            }
    
            //      
            for (int i=0;iwhile (array[i] != i){
                    if (array[i] == array[array[i]]){
                        return array[i];
                    }
                    int temp = array[i];
                    array[i] = array[temp];
                    array[temp] = temp;
                }
            }
           return -1;
        }
    
        /**
         *     
         * @param args 0
         */
        public static void main(String[] args) {
            int[] array = {2,3,1,0,3,5,2};
            int dup = duplicate(array);
            if (dup == -1)
                System.out.println("       ");
            else if (dup == -2)
                System.out.println("          ");
            else
                System.out.println("     :"+dup);
        }

    アルゴリズムの考え方
    この配列の各数値を最初から最後まで順次スキャンします.下のiの数字mにスキャンすると、まずmがiに等しいかどうかを比較します.もしそうであれば、次の数字をスキャンします.そうでなければ、mと下にmと表記された数字を比較し、等しい場合、mは重複する数字である.等しくない場合は、iとmと表記された数字を交換し、mを適切な位置に戻します.次にこの比較、交換の過程を繰り返して、私たちが繰り返しの数字を発見するまで知っています.アルゴリズムには二重ループがあるが,第二層ループは最大2回行われるため,全時間複雑度はO(n)である.また,すべての操作が本配列で行われるため,空間的複雑度はO(1)である.
    面接問題4の2次元配列での検索
    2 D配列では、各行が左から右に増加し、各列が上から下に増加します.関数を完了し、このような2次元配列と整数を入力して、配列にその整数が含まれているかどうかを判断してください.
    テストケース
  • 配列は、指定する数字
  • を含む.
  • 配列は、指定する数字
  • を含まない.
  • 配列不適合(配列が空)
  • インプリメンテーションコード
    /**
         *        ,                 ,              。
         *        ,                ,            。
         * @param array   
         * @param num       
         * @return   :true    :false 
         */
        public static boolean findNumber(int[][] array, int num){
            //      
            if (array == null)
                return false;
            //    
            int i=0;
            int j=array[0].length -1;
            while (i =0) {
                if (num == array[i][j])
                    return true;
                else if (num < array[i][j])
                    j--;
                else
                    i++;
            }
            return false;
        }
    
     public static void main(String[] args) {
            int[][] arr={{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}};
            System.out.println(findNumber(arr,7));
        }
    

    アルゴリズムの考え方
    配列の右上隅の数値を優先的に選択します.この数値が検索する数値に等しい場合、検索プロセスは終了します.検索する数値より大きい場合は、その数値が存在する列を削除します.検索する数値より小さい数値の場合は、その数値の行を削除します.これにより、一歩一歩範囲を狭めることができます.
    面接問題5スペースの置換
    文字列の各スペースを%20に置き換える関数を実装してください.例えば「We are happy.」と入力し、「we%20 are%20 happy.」と出力します.
    テストケース
  • 入力文字列は、スペース
  • を含む
  • 入力文字列にスペースは含まれません
  • 特殊入力テスト(文字列が空、文字列が空、文字列が空、文字列に連続する複数のスペース)
  • インプリメンテーションコード
    /**
         *          
         * @param str    
         * @return        
         */
        public static char[] replaceSpace(String str){
            if(str == null || str.length() == 0)
                return null;
    
            int spCount=0;
            int i,j;
            char[] chars = str.toCharArray();
            //       
            for (char aChar : chars) {
                if (aChar == ' ')
                    spCount++;
            }
            //        ,         2
            char[] newChars = new char[chars.length+2*spCount];
    
            i=chars.length-1;
            j=newChars.length-1;
            //                   
            while (i != j) {
                while (chars[i] != ' ') {
                    newChars[j--] = chars[i--];
                }
                newChars[j--] = '0';
                newChars[j--] = '2';
                newChars[j--] = '%';
                //    ,    
                i--;
            }
            //                    
            for (int m=i;m>=0;m--){
                newChars[m] = chars[m];
            }
            return newChars;
        }
    
        public static void main(String[] args) {
            String s0 = "we are happy.";
            char[] s1 = replaceSpace(s0);
            System.out.println(new String(s1));
            //              
            //System.out.println(s1.toString());
            /* System.out.print()       ,       toString()  ,       。
                 ,  toString()            +hashCode()
             */
    
        }

    アルゴリズムの考え方
    後からコピーするとO(n 2)の時間オーバーヘッドが発生し,空間が計算され,後からコピーするとO(n)の時間オーバーヘッドだけが必要となる.まず配列を1回巡り,スペースの個数を算出する.新しい配列を作成します.サイズは元の配列のサイズにスペースの個数を乗算します.2つの下付き文字を宣言し、それぞれ新しい古い配列の末尾を指し、順番にコピーし、スペースに遭遇するとスペースを処理します.
    面接問題6末尾からチェーンテーブルを印刷する
    チェーンテーブルのヘッダノードを入力し、各ノードの値を末尾から逆に印刷します.チェーンテーブルノードは次のように定義されます.
        class ListNode{
            int m_nKey;
            ListNode m_pNext;
        }

    テストケース
  • 機能テスト(入力チェーンテーブルには複数のノードがある;入力チェーンテーブルには1つのノードしかない)
  • 特殊入力テスト(入力チェーンヘッダノードが空)
  • インプリメンテーションコード
    /**
     *           ,                。        :
     */
    public class InterQuestions6 {
        /**
         *      
         */
        static class ListNode{
            int m_nKey;
            ListNode m_pNext;
        }
    
        /**
         *          :  java  
         * @param list          
         */
        public static  void reverseListWithStack(ListNode list){
            if (list == null)
                return;
    
            Stack stack = new Stack<>();
            while (list != null){
                stack.push(list);
                list = list.m_pNext;
            }
            while (!stack.empty()){
                System.out.println(stack.pop().m_nKey+"  ");
            }
        }
    
        /**
         *          :    .
         *          ,        ,            。            。
         * @param list   
         */
        public static void reverseListWithRecursion(ListNode list){
            if (list == null)
                return;
    
            if (list.m_pNext != null)
                reverseListWithRecursion(list.m_pNext);
                System.out.print(list.m_nKey+"  ");
        }
    
        public static void main(String[] args) {
            ListNode list = new ListNode();
            list.m_nKey = 0;
            for (int i=7;i>0;i--){
                ListNode newNode = new ListNode();
                newNode.m_nKey = i;
                newNode.m_pNext = list.m_pNext;
                list.m_pNext = newNode;
            }
    
            reverseListWithStack(list);
            reverseListWithRecursion(list);
        }
    
    }

    アルゴリズムの考え方
    逆シーケンス出力が必要なため、「先進後出」に似ています.実装方法1:スタックを用いて実装できることを考慮する.チェーンテーブルを1回巡り、チェーンテーブルの要素をスタックに一度に押し込みます.最後に、スタック内の要素を順番にポップアップします.実現方式2:再帰は本質的にスタック構造である.だから再帰を使って実現することができます.
    面接問題7ツリーの再構築
    ツリーの前順および中順の結果を入力し、ツリーを再構築します.入力された前シーケンスおよび中シーケンスの結果に重複する数値が含まれていないとします.例えば、前シーケンス遍歴シーケンス{1,2,4,7,3,5,6,8}および中シーケンス遍歴シーケンス{4,7,2,1,5,3,8,6}を入力すると、下図に示すようなツリーが再構築され、そのヘッダノードが出力される.ツリーノードの定義は次のとおりです.
    class BinaryTreeNode{
        int m_nValue;
        BinaryTreeNode m_pLeft;
        BinaryTreeNode m_pRight;
    }

    テストケース
  • 普通二叉木
  • 特殊なツリー(すべてのノードに右サブノードのツリーがあるかどうか;すべてのノードに左サブノードのツリーがない;1つのノードのツリーしかない;
  • インプリメンテーションコード
    /**
     *        
     */
    class BinaryTreeNode{
        int m_nValue;
        BinaryTreeNode m_pLeft;
        BinaryTreeNode m_pRight;
    
        public BinaryTreeNode(int m_nValue, BinaryTreeNode m_pLeft, BinaryTreeNode m_pRight) {
            this.m_nValue = m_nValue;
            this.m_pLeft = m_pLeft;
            this.m_pRight = m_pRight;
        }
    }
    
    public class InterQuestions7 {
    
        /**
         *           ,   
         * @param pre       
         * @param in       
         * @return     
         */
        public static BinaryTreeNode rebuildBinaryTree(int[] pre, int[] in){
            /*int root = pre[0];
            for (int i=0;i
            if (pre == null || in == null)
                return null;
    
            return rebuildCore(pre,in,0,pre.length-1,0,in.length-1);
        }
    
    
        /**
         *             
         * @param pre       
         * @param in       
         * @param preStart       
         * @param preEnd       
         * @param inStart       
         * @param inEnd       
         * @return        
         */
        public static BinaryTreeNode rebuildCore(int[] pre, int[] in,
                                                 int preStart,int preEnd,
                                                 int inStart, int inEnd ){
            int rootValue = pre[preStart];
            BinaryTreeNode tree = new BinaryTreeNode(rootValue,null,null);
            if (preStart == preEnd && inStart == inEnd)
                return tree;
            int root =0;
            //              
            for (root=inStart;root<=inEnd;root++){
                if (rootValue == in[root])
                    break;
            }
            //         
            int leftLength = root-inStart;
            int rightLength = inEnd - root;
            if (leftLength > 0)
                tree.m_pLeft=rebuildCore(pre,in,preStart+1,preStart+leftLength,inStart,root-1);
            if (rightLength > 0)
                tree.m_pRight=rebuildCore(pre,in,preStart+leftLength+1,preEnd,root+1,inEnd);
            return tree;
        }
    
        /**
         *         
         */
        public static void preTraversalBinTree(BinaryTreeNode tree){
            if (tree == null)
                return;
            System.out.print(tree.m_nValue+",");
            preTraversalBinTree(tree.m_pLeft);
            preTraversalBinTree(tree.m_pRight);
        }
    
        /**
         *         
         */
        public static void inTraversalBinTree(BinaryTreeNode tree){
            if (tree == null)
                return;
            inTraversalBinTree(tree.m_pLeft);
            System.out.print(tree.m_nValue+",");
            inTraversalBinTree(tree.m_pRight);
        }
        /**
         *         
         */
        public static void postTraversalBinTree(BinaryTreeNode tree){
            if (tree == null)
                return;
            postTraversalBinTree(tree.m_pLeft);
            postTraversalBinTree(tree.m_pRight);
            System.out.print(tree.m_nValue+",");
        }
    
        /**
         *    ,    
         * @param args   
         */
        public static void main(String[] args) {
            int[] pre = {1,2,4,7,3,5,6,8};
            int[] in = {4,7,2,1,5,3,8,6};
            BinaryTreeNode tree= rebuildBinaryTree(pre,in);
            System.out.println("      :");
            preTraversalBinTree(tree);
            System.out.println();
            System.out.println("      :");
            inTraversalBinTree(tree);
            System.out.println();
            System.out.println("      :");
            postTraversalBinTree(tree);
        }
    }
    
    /*    
          :
    1,2,4,7,3,5,6,8,
          :
    4,7,2,1,5,3,8,6,
          :
    7,4,2,5,8,6,3,1,
    Process finished with exit code 0
    */

    アルゴリズムの考え方
    前のシーケンス遍歴シーケンスの最初の数値は、ルートノードの値です.スキャン中にシーケンスをシーケンスして、ルートノードの位置を決定します.中順遍歴の特徴によれば,ルートノードの値の前の値は左サブツリーのノード値であり,ルートノードの後ろの値は右サブツリーの値であるためである.左サブツリーノードと右サブツリーノードの値を前シーケンスループと中シーケンスループで分割した後、関数を再帰的に呼び出して左サブツリーと右サブツリーをそれぞれ構築することができます.
    面接問題8ツリーの次のノード
    ツリーとノードの1つを指定します.シーケンスの次のノードを見つけるにはどうすればいいですか?ツリー内のノードには、左ノードと右ノードをそれぞれ指す2つのポインタと、親ノードを指す1つのポインタがあります.
    テストケース
  • 普通二叉木
  • 特殊ツリー(すべてのノードに右サブノードまたは左サブノードがないツリー;ツリーが空)
  • インプリメンテーションコード
    public class interQuestions8 {
        static class BinaryTreeNode{
            String value;
            BinaryTreeNode left;
            BinaryTreeNode right;
            BinaryTreeNode parent;
    
            public BinaryTreeNode(String value, BinaryTreeNode left, BinaryTreeNode right, BinaryTreeNode parent) {
                this.value = value;
                this.left = left;
                this.right = right;
                this.parent = parent;
            }
        }
    
        /**
         *             
         * @param treeNode     
         * @return        null
         */
        public static BinaryTreeNode getNext(BinaryTreeNode treeNode){
            if (treeNode == null)
                return null;
    
            BinaryTreeNode nextNode = null;
            if (treeNode.right != null){
                BinaryTreeNode rNode = treeNode.right;
                while (rNode.left != null){
                    rNode = rNode.left;
                }
                nextNode = rNode;
            }else if (treeNode.parent != null){
                BinaryTreeNode current = treeNode;
                BinaryTreeNode parent = treeNode.parent;
                while (parent != null && current == parent.right){
                    current = parent;
                    parent = current.parent;
                }
                nextNode = parent;
            }
            return nextNode;
        }
    
        /**
         * main      
         * @param args   
         */
        public static void main(String[] args) {
            //           
            BinaryTreeNode root = new BinaryTreeNode("a",null,null,null);
            root.left = new BinaryTreeNode("b",null,null,root);
            root.right = new BinaryTreeNode("c",null,null,root);
            root.left.left = new BinaryTreeNode("d",null,null,root.left);
            root.left.right = new BinaryTreeNode("e",null,null,root.left);
    
            BinaryTreeNode node = getNext(root.left);
            //BinaryTreeNode node = getNext(root.left.right);
            //BinaryTreeNode node = getNext(root.right);
            if (node == null)
                System.out.println("               ");
            else
            System.out.println("          :"+node.value);
        }
    }
    

    アルゴリズムの考え方
    分3の場合:一、ノードに右サブツリーがある場合、右サブツリーの一番左のノードが次のノードになります.二、このノードに右のサブツリーがなく、親ノードの左のサブノードである場合、彼の親ノードは次のノードである.三、このノードは右のサブツリーがなく、親ノードの右のサブノードであるので、順番に上を回って、あるノードが親ノードの左のサブノードであり、その親ノードが次のノードであることを知っています.
    面接問題9は2つのスタックでキューを実現する
    2つのスタックで1つのキューを実現します.2つの関数appendTailとdeleteHeadを実装し、キューの末尾にノードを挿入し、キューの先頭にノードを削除する機能をそれぞれ完了してください.
    テストケース
  • 空のキューに要素
  • を追加、削除する
  • 空でないキューに削除要素
  • を追加
  • キューが空の
  • になるまで要素を連続的に削除する.
    インプリメンテーションコード
    /**
     *          
     */
    public class InterQuestions9 {
        private Stack stack1 = new Stack<>();
        private Stack stack2 = new Stack<>();
    
        /**
         *           
         * @param tail       
         */
        public void appendTail(int tail){
            stack1.push(tail);
        }
    
        /**
         *          
         * @return      
         */
        public  int deleteHead() throws Exception {
            if (stack2.empty()){
                if (stack1.empty())
                    throw new Exception("queue is empty.");
                while (!stack1.empty()){
                    stack2.push(stack1.pop());
                }
            }
            return stack2.pop();
        }
    
        public static void main(String[] args) throws Exception {
            InterQuestions9 queue = new InterQuestions9();
            //queue.deleteHead();
            queue.appendTail(1);
            queue.appendTail(2);
            queue.appendTail(3);
            System.out.println(queue.deleteHead());
            queue.appendTail(4);
            System.out.println(queue.deleteHead());
            System.out.println(queue.deleteHead());
            System.out.println(queue.deleteHead());
    
        }
    }
    /*     
    1
    2
    3
    4
     */

    アルゴリズムの考え方
    stack 1は入力を受信し、キューから要素を削除する場合は、Stack 2が空であるかどうかを確認し、空でない場合は直接要素をポップアップします.stack 2が空であれば、stack 1が空であるか否かを判断し、stack 1が空であれば、キューに要素は存在しない.stack 1が空でない場合は、stack 1の要素を順番にポップアップしてstack 2に押し込み、stack 2が要素をポップアップします.