跟着左神学算法:详解前缀树和贪心算法

174 阅读2分钟

详解前缀树和贪心算法

前缀树

前缀树定义

前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。

 public class TrieNode {
    // 构建树的时候这个节点通过多少次
    public int pass;
    // 有多少字符串以它为结束点
    public int end;

    // 可以换成 HashMap
    public TrieNode[] nexts;

    public TrieNode() {
        pass = 0;
        end = 0;
        nexts = new TrieNode[26];
    }
}

生成前缀树

public class Trie {

    public TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    public void insert(String word) {
        if (word == null) {
            return;
        }
        char[] chs = word.toCharArray();
        TrieNode node = root;
        node.pass++;
        int index = 0;
        for (char ch : chs) {
            index = ch - 'a';
            if (node.nexts[index] == null) {
                node.nexts[index] = new TrieNode();
            }
            node = node.nexts[index];
            node.pass++;
        }
        node.end++;
    }

    public void delete(String word) {
        if (word == null || search(word) <= 0) {
            return;
        }
        TrieNode node = root;
        int index = 0;
        for (char ch : word.toCharArray()) {
            node.pass--;
            index = ch - 'a';
            if (node.nexts[index].pass == 0) {
                node.nexts[index] = null;
                return;
            }
            node = node.nexts[index];
        }
        node.end--;
    }

    public int search(String word) {
        if (word == null) {
            return true;
        }

        TrieNode node = root;
        for (char ch : word.toCharArray()) {
            if (node.nexts[ch - 'a'] == null) {
                return 0;
            }
            node = node.nexts[ch - 'a'];
        }
        return node.end;
    }

    public int prefixNumber(String pre) {
        if (pre == null) {
            return 0;
        }
        TrieNode node = root;
        for (char ch : pre.toCharArray()) {
            if (node.nexts[ch - 'a'] == null) {
                return 0;
            }
            node = node.nexts[ch - 'a'];
        }
        return node.pass;
    }
}

贪心算法

贪心算法定义

在某一个标准下,优先考虑最满足标准的样本,最后考虑不满足标准的样本,最终得到一个答案的算法,叫做贪心算法。
从局部最优 => 全局最优

题目一

给会议室安排日程。

贪心策略:

  1. 按照开始时间安排,先安排开始时间早的(反例:有一个从开始到最后的会议)
  2. 按照持续时间安排,先安排持续时间短的(反例:有一个会议横跨两个会议之间并且特别短)
  3. 按照结束时间安排,先安排结束时间早的

题目二

贪心算法解题思路

  1. 实现一个不依靠贪心策略的解法X,可以用最暴力的尝试
  2. 猜测贪心策略,并验证

N皇后问题

   public int totalNQueens(int n) {
        int[] record = new int[n];
        return help(0, record, n);
    }


    public int help(int i, int[] record,int n) {
        if(i == n) {
            return 1;
        }
        int res = 0;
        for(int j = 0; j < n; j++) {
            if(isValid(record, i, j)) {
                record[i] = j;
                res += help(i + 1, record, n);
            }
        }
        return res;
    }

    public boolean isValid(int[] record, int i, int j) {
        for(int t = 0; t < i; t++) {
            if(record[t] == j) {
                return false;
            }
            if(t - i == record[t] - j) {
                return false;
            }
            if(t - i == j - record[t]) {
                return false;
            }
        }
        return true;
    }

N皇后问题优化

N比较大的时候有10倍的差距,较小的时候没有差距

    public int totalNQueens2(int n) {
        int limit = n == 32 ? -1 : (1 << n) - 1;
        return help(limit, 0, 0, 0);
    }

    public int help(int limit, int colLimit, int leftLimit, int rightLimit) {
        if(colLimit == limit) {
            return 1;
        }

        int pos = limit & ~(colLimit | leftLimit | rightLimit);
        int res = 0;
        int mostRightOne = 0;
        while (pos != 0) {
            mostRightOne = pos & (~pos + 1);
            pos = pos - mostRightOne;
            res += help(limit, colLimit | mostRightOne
                    , (leftLimit | mostRightOne) << 1,
                    (rightLimit | mostRightOne) >> 1);
        }
        return res;
    }