1. 程式人生 > >劍指offer題解(十一):c++&java

劍指offer題解(十一):c++&java

序列化二叉樹

題目描述

請實現兩個函式,分別用來序列化和反序列化二叉樹

  1. 對於序列化:使用前序遍歷,遞迴的將二叉樹的值轉化為字元,並且在每次二叉樹的結點不為空時,在轉化val所得的字元之後新增一個’ , ‘作為分割。對於空節點則以 ‘#’ 代替。
    1. 對於反序列化:按照前序順序,遞迴的使用字串中的字元建立一個二叉樹(特別注意:在遞迴時,遞迴函式的引數一定要是char ** ,這樣才能保證每次遞迴後指向字串的指標會隨著遞迴的進行而移動!!!)

java

public class Solution {
    String Serialize(TreeNode root)
    {
        if
(root==null) return ""; StringBuilder sb = new StringBuilder(); Serialize2(root,sb); return sb.toString(); } void Serialize2(TreeNode root, StringBuilder sb) { if(root==null) { sb.append("#,"); return; } sb.append(root.val); sb.append(','
); Serialize2(root.left,sb); Serialize2(root.right,sb); } int index = -1; TreeNode Deserialize(String str) { if(str.length()==0) return null; String [] strs = str.split(","); return Deserialize2(strs); } TreeNode Deserialize2(String[] strs) { index
++; if(!strs[index].equals("#")) { TreeNode root = new TreeNode(0); root.val = Integer.parseInt(strs[index]); root.left = Deserialize2(strs); root.right = Deserialize2(strs); return root; } return null; } }

c++

class Solution {
public:
    vector<int> buf;
    void dfs1(TreeNode *root) {
        if(!root) buf.push_back(0xFFFFFFFF);
        else {
            buf.push_back(root->val);
            dfs1(root->left);
            dfs1(root->right);
        }
    }
    TreeNode* dfs2(int* &p) {
        if(*p==0xFFFFFFFF) {
            p++;
            return NULL;
        }
        TreeNode* res=new TreeNode(*p);
        p++;
        res->left=dfs2(p);
        res->right=dfs2(p);
        return res;
    }
    char* Serialize(TreeNode *root) {
        buf.clear();
        dfs1(root);
        int bufSize=buf.size();
        int *res=new int[bufSize];
        for(int i=0;i<bufSize;i++) res[i]=buf[i];
        return (char*)res;
    }
    TreeNode* Deserialize(char *str) {
        int *p=(int*)str;
        return dfs2(p);
    }
};

字串的排列

題目描述

輸入一個字串,按字典序打印出該字串中字元的所有排列。例如輸入字串 abc,則打印出由字元 a, b, c 所能排列出來的所有字串 abc, acb, bac, bca, cab 和 cba。
ps:輸入一個字串,長度不超過9(可能有字元重複),字元只包括大小寫字母。

解題思路

c++

class Solution {
public:
    vector<string> Permutation(string str) {
        //可以用遞迴來做
        vector<string> array;
        if(str.size()==0)
            return array;
        Permutation(array, str, 0);
        sort(array.begin(), array.end());
        return array;
    }

    void Permutation(vector<string> &array, string str, int begin)//遍歷第begin位的所有可能性
    {
        if(begin==str.size()-1)
            array.push_back(str);
        for(int i=begin; i<=str.size()-1;i++)
        {
            if(i!=begin && str[i]==str[begin])//有重複字元時,跳過
                continue;
            swap(str[i], str[begin]);//當i==begin時,也要遍歷其後面的所有字元;
                                    //當i!=begin時,先交換,使第begin位取到不同的可能字元,再遍歷後面的字元
            Permutation(array, str, begin+1);//遍歷其後面的所有字元;

            swap(str[i], str[begin]);//為了防止重複的情況,還需要將begin處的元素重新換回來

            /*舉例來說“abca”,為什麼使用了兩次swap函式
                交換時是a與b交換,遍歷;
                交換時是a與c交換,遍歷;(使用一次swap時,是b與c交換)
                交換時是a與a不交換;
                */
        }
    }
};

java

import java.util.*;

public class Solution {

    public ArrayList<String> Permutation(String str) {
        ArrayList<String> result = new ArrayList<String>() ;
        if(str==null || str.length()==0) { return result ; }

        char[] chars = str.toCharArray() ;
        TreeSet<String> temp = new TreeSet<>() ;
        Permutation(chars, 0, temp);
        result.addAll(temp) ;
        return result ;
    }

    public void Permutation(char[] chars, int begin, TreeSet<String> result) {
        if(chars==null || chars.length==0 || begin<0 || begin>chars.length-1) { return ; }

        if(begin == chars.length-1) {
            result.add(String.valueOf(chars)) ;
        }else {
            for(int i=begin ; i<=chars.length-1 ; i++) {
                swap(chars, begin, i) ;

                Permutation(chars, begin+1, result);

                swap(chars, begin, i) ;
            }
        }
    }

    public void swap(char[] x, int a, int b) {
        char t = x[a];
        x[a] = x[b];
        x[b] = t;
    }

}

陣列中出現次數超過一半的數字

題目描述

陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。例如輸入一個長度為9的陣列{1,2,3,2,2,2,5,4,2}。由於數字2在陣列中出現了5次,超過陣列長度的一半,因此輸出2。如果不存在則輸出0。

解題思路

多數投票問題,可以利用 Boyer-Moore Majority Vote Algorithm 來解決這個問題,使得時間複雜度為 O(N)。

使用 cnt 來統計一個元素出現的次數,當遍歷到的元素和統計元素不相等時,令 cnt–。如果前面查找了 i 個元素,且 cnt == 0 ,說明前 i 個元素沒有 majority,或者有 majority,但是出現的次數少於 i / 2 ,因為如果多於 i / 2 的話 cnt 就一定不會為 0 。此時剩下的 n - i 個元素中,majority 的數目依然多於 (n - i) / 2,因此繼續查詢就能找出 majority。

java

public int MoreThanHalfNum_Solution(int[] nums) {
    int majority = nums[0];
    for (int i = 1, cnt = 1; i < nums.length; i++) {
        cnt = nums[i] == majority ? cnt + 1 : cnt - 1;
        if (cnt == 0) {
            majority = nums[i];
            cnt = 1;
        }
    }
    int cnt = 0;
    for (int val : nums)
        if (val == majority)
            cnt++;
    return cnt > nums.length / 2 ? majority : 0;
}

c++

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers)
    {
        if(numbers.empty()) return 0;

        // 遍歷每個元素,並記錄次數;若與前一個元素相同,則次數加1,否則次數減1
        int result = numbers[0];
        int times = 1; // 次數

        for(int i=1;i<numbers.size();++i)
        {
            if(times == 0)
            {
                // 更新result的值為當前元素,並置次數為1
                result = numbers[i];
                times = 1;
            }
            else if(numbers[i] == result)
            {
                ++times; // 相同則加1
            }
            else
            {
                --times; // 不同則減1               
            }
        }

        // 判斷result是否符合條件,即出現次數大於陣列長度的一半
        times = 0;
        for(int i=0;i<numbers.size();++i)
        {
            if(numbers[i] == result) ++times;
        }

        return (times > numbers.size()/2) ? result : 0;
    }
};

最小的 K 個數

題目描述

輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。

python

先皮一下

import heapq
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        if k> len(tinput):
            return []
        return list(heapq.nsmallest(k,tinput))

用api也可以通過相應的測試

解題思路

快速選擇
複雜度:O(N) + O(1)
只有當允許修改陣列元素時才可以使用
快速排序的 partition() 方法,會返回一個整數 j 使得 a[l..j-1] 小於等於 a[j],且 a[j+1..h] 大於等於 a[j],此時 a[j] 就是陣列的第 j 大元素。可以利用這個特性找出陣列的第 K 個元素,這種找第 K 個元素的演算法稱為快速選擇演算法。

public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
    ArrayList<Integer> ret = new ArrayList<>();
    if (k > nums.length || k <= 0)
        return ret;
    int kthSmallest = findKthSmallest(nums, k - 1);
    // findKthSmallest 會改變陣列,使得前 k 個數都是最小的 k 個數
    for (int i = 0; i < k; i++)
        ret.add(nums[i]);
    return ret;
}

public int findKthSmallest(int[] nums, int k) {
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int j = partition(nums, l, h);
        if (j == k)
            break;
        if (j > k)
            h = j - 1;
        else
            l = j + 1;
    }
    return nums[k];
}

private int partition(int[] nums, int l, int h) {
    // 切分元素
    int parti = nums[l];
    int i = l, j = h + 1;
    while (true) {
        while (i != h && nums[++i] < parti) ;
        while (j != l && nums[--j] > parti) ;
        if (i >= j)
            break;
        swap(nums, i, j);
    }
    swap(nums, l, j);
    return j;
}

private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
}

最小堆的方法

複雜度:O(NlogK) + O(K)
特別適合處理海量資料
應該使用大頂堆來維護最小堆,而不能直接建立一個小頂堆並設定一個大小,企圖讓小頂堆中的元素都是最小元素。

維護一個大小為 K 的最小堆過程如下:在新增一個元素之後,如果大頂堆的大小大於 K,那麼需要將大頂堆的堆頂元素去除。

public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
    if (k > nums.length || k <= 0)
        return new ArrayList<>();
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
    for (int num : nums) {
        maxHeap.add(num);
        if (maxHeap.size() > k)
            maxHeap.poll();
    }
    ArrayList<Integer> ret = new ArrayList<>(maxHeap);
    return ret;
}