一道演算法題的分析

tony0087發表於2021-09-09

一位前輩發給我一道演算法題,這裡整理一下自己的解題思路。

請設計一種處理演算法能高效的檢查出字串中第一次出現的重複字元。給定的字串長度小於等於500個字元,包含字母、數字、英文符號。

圖片描述

查詢第一次出現的重複字元...

  1. 逐個遍歷字元,判斷當前字元之前的字元陣列中是否出現此字元,第一次出現則跳出迴圈

考察:在字元陣列中如何快速的查詢某個字元。

知識:

  • 字元是Java中的原始(Primitive)型別,可以和int型別直接轉換的。它的包裝類為Character.
  • 要查詢字元陣列是無序的,要不要對無序陣列進行排序?不需要排序:排序演算法最快的時間複雜度也為O(nlogn),而遍歷一遍的時間複雜度為O(n),所以如果只進行一次操作,排序不如遍歷。
  • 如果能進行O(1),或者比O(1)略差一點的操作就好了,可以考慮使用HashMap,HashSet直接取值,HashMap內部使用陣列來儲存資料。

實現

  1. 普通版本,因為看到字串長度小於500個字元,查詢重複的字元,資料量比較小,普通的兩層迴圈在資料量小的時候也可以嘗試。時間複雜度為O(n^2)
    /**
     * 常規的查詢第一個重複字串的方法
     * @return 當字串為空時,返回null
     */
    public static Character normalFind(String str){

        if (str == null || "".equals(str)) {
            return null;
        }

        if (str.length() > 500){
            //日誌,根據文件,判斷是否處理大字串
            System.out.println("warn 輸入的字元長度過大");
        }

        for (int i = 1; i 
  1. 剝離HashMap實現部分,自己實現HashMap內部的陣列存取,資料量比較小,採用簡化的連結串列。
    • 考慮到資料量是在是太小的時候,這裡閾值設定為15,長度小於15時使用常規的查詢來操作,省的建立物件分配空間
    • 可設定內部要索引的陣列容量。

依次遍歷每個字元,遍歷過的字元進行hashCode,字元的hashCode為自身代表的int值,hashCode與陣列容量求餘,將字元再以連結串列的形式存在陣列中,如果其它字元的求出的下標重複,將當前節點加入到連結串列的最後節點。

    static class Node {
        /**
         * 存放下個節點
         */
        Node next;

        /**
         * 存放當前節點的值
         */
        int value;

        public Node() {
        }

        public Node(Node next, int value) {
            this.next = next;
            this.value = value;
        }

    }

      public static Character myFind(String str, Integer capacity) {
        if (str == null || "".equals(str)) {
            return null;
        }
        if (str.length() > 500) {
            //日誌,根據說明判斷是否處理
            System.out.println("warn 輸入的字元長度過大");
        }
        if (str.length() = 500){
            capacity = 500;
        }

        Node[] chars = new Node[capacity];
        for (int i = 0; i 
  1. 採用HashSet,Java內建資料結構進行資料的存取

如果待查詢的字元陣列中不存在字元,就加入新的字元,如果存在字元就返回當前字元

    public static Character setFind(String str) {
        if (str == null || "".equals(str)) {
            return null;
        }

        if (str.length() > 500) {
            //日誌,根據說明判斷是否處理
            System.out.println("warn 輸入的字元長度過大");
        }

        HashSet hashSet = new HashSet();
        for (int i = 0; i 

測試結果

測試程式碼如下:

 /**
     * 1. 字元型別為數字,字母,特殊字元
     * 2. 陣列時連續的記憶體空間,可以直接根據下標來查詢元素
     * 3. 計算機對數字比對字元敏感,因此字元的儲存採用數字形式
     */
  public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("請在執行時輸入字串");
            return;
        }

        for (int i = 0; i 

編譯執行程式

javac me/aihe/exam/FindFirstRepeatdChar.java 

執行程式:
圖片描述

暫時只用了兩個長度的字串測試,可以多執行幾次檢視效果, 一般耗時和字串的長度與指定的陣列容量有關。

分析

  • 這次寫的演算法是犧牲空間來換取時間,普通的按順序逐個對比不需要額外的空間。
  • 演算法參考HashMap內部實現,但對其做了一些精簡處理,Node節點這次儲存的是字元字元的hashCode值,字元與int可以互相轉換,Node只儲存值就好了。
  • 省去了HashMap的resize()操作,直接在查詢的時候指定陣列的容量。陣列的空間越大越不容易產生下標衝突,可以直接進行O(1)取出資料。

最後

最近越來越意識到演算法的重要性,也在惡補中,穩紮穩打,希望能早日實現目標。

附錄

完整程式碼:

package me.aihe.exam;

import java.math.BigDecimal;
import java.util.HashSet;

/**
 * @author aihe 2018/6/27
 */
public class FindFirstRepeatdChar {

    static class Node {
        /**
         * 存放下個節點
         */
        Node next;

        /**
         * 存放當前節點的值
         */
        int value;

        public Node() {
        }

        public Node(Node next, int value) {
            this.next = next;
            this.value = value;
        }

    }

    /**
     * 普通的查詢第一個重複字串的方法
     *
     * @return
     */
    public static Character normalFind(String str) {

        if (str == null || "".equals(str)) {
            return null;
        }

        if (str.length() > 500) {
            //日誌,根據說明判斷是否處理
            System.out.println("warn 輸入的字元長度過大");
        }
        //從第一個下標開始找
        for (int i = 1; i  500) {
            //日誌,根據說明判斷是否處理
            System.out.println("warn 輸入的字元長度過大");
        }
        if (str.length() = 500){
            capacity = 500;
        }

        Node[] chars = new Node[capacity];
        for (int i = 0; i  500) {
            //日誌,根據說明判斷是否處理
            System.out.println("warn 輸入的字元長度過大");
        }

        if (capacity == null){
            capacity = 16;
        }

        if (capacity >= 500){
            capacity = 500;
        }
        HashSet hashSet = new HashSet(capacity);
        for (int i = 0; i ?[]{}交換程式碼由學會制定單符編方案基文字資料。起始於後期年最初是美家供不同計算機在相互通訊時用作共遵守的西字它已被國際標準化組織定為國際標準,稱為ISO 646標準。適用於所有拉丁文字字母";
        if (args.length == 0) {
            System.out.println("請在執行時輸入字串");
            return;
        }

        for (int i = 0; i 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1747/viewspace-2803354/,如需轉載,請註明出處,否則將追究法律責任。

相關文章