8.暴力递归---C++版左程云耗时100天打造算法与数据一周刷爆LeetCode

本文介绍了如何使用Java实现前缀树(TrieTree),包括插入、查找、删除操作,以及如何利用前缀树扩展功能以计算前缀出现次数。同时,文中提到了贪心算法的应用实例,如字符串拼接最小字典序和项目时间安排等。
摘要由CSDN通过智能技术生成

前缀树

        某些字符串在生成这些前缀树的过程中是这么生成的。前缀树也是一颗树结构假设把“abc”放入这颗树中。看头结点有没有走向a的路,每一个字母是填在路上的,不是填在点上面的。没有就加入,通过这种方式把abc加入到树中。如果把“abd”加到树上,从头结点开始有走向a的路吗?有,就复用,不重复建。再看从a到达的结点往后有没有b的路,有就复用,但是之后没有走向d的路。就建出来。一个字符串加的过程中总是从头结点开始,依次看有没有沿途的路,如果有,复用,如果没有就建出来。干净的结点的话:可以用来判断这些字符串以什么开头。增加功能:看字符串中有没有“be”,只能知道树中有没有“be”并不知道“be”出现了几次。所以在结点上增加一个数据项,这个数据项的功能是看有多少个字符串是以当前的结点结尾的。把某一个数据项加到结点上,让这个结点本身的值的内容丰富起来之后,那么这个前缀树的功能就可以扩充了。
扩充功能,就是丰富结点的数据项,与前缀有关的。加每个字符串的代价就是字符串的长度,查的时候也是。和样本量无关和样本的长度有关。

public class TrieTree {

	public static void main(String[] args) {
		Trie trieTree=new Trie();
		String[] arr= {"abc","abcd","ade","abcde"};
		for(String s:arr) {
			trieTree.insert(s);
		}
//		trieTree.insert("abc");
//		System.out.println(trieTree.search("abc")!=0?"存在":"不存在");
//		trieTree.delete("abc");
//		System.out.println(trieTree.search("abc")==1?"存在":"不存在");
		System.out.println(trieTree.prefixNumber("abcd"));
	}

}
//实现类
class Trie{
	private Node root;	//前缀树的根节点
	
	public Trie() {
		root=new Node();
	}
	
	//插入新字符串
	public void insert(String word) {
		if(word==null) {
			return;
		}
		char[] arr=word.toCharArray();	//将插入的字符串转化为char数组
		Node node=root;
		int path=0;
		for(char c:arr) {
			path=c-'a';
			if(node.next[path]==null) {	//该节点不存在
				node.next[path]=new Node();	
			}
			node=node.next[path];
			node.pass++;
		}
		node.end++;
	}
	
	//查找该字符串是否存在
	public int search(String word) {
		if(word==null) {
			return 0;
		}
		char[] arr=word.toCharArray();
		int path=0;
		Node node=root;
		for(char c:arr) {
			path=c-'a';
			if(node.next[path]==null) {	//查找的字符对应的节点不存在
				return 0;
			}
			node=node.next[path];
		}
		return node.end;
	}
	
	
	public void delete(String word) {	
		if(search(word)!=0) {
			char[] arr=word.toCharArray();
			int path=0;
			Node node=root;	//从root开始
			node.pass--;	//该节点的pass减一
			for(char c:arr) {
				path=c-'a';
				if(--node.next[path].pass==0) {
					node.next[path]=null;
					return;
				}
				node=node.next[path];
			}
			node.end--;	//结尾节点的end减一
		}
	}
	
	//查找以该pre为前缀的字符的数量
	public int prefixNumber(String pre) {
		if(pre==null) {
			return 0;
		}
		char[] arr=pre.toCharArray();
		Node node=root;
		int path=0;
		for(char c:arr) {
			path=c-'a';
			if(node.next[path]==null) {
				return 0;
			}
			node=node.next[path];
		}
		return node.pass;
	}
	
}

//节点类
class Node{
	public int pass;	//经过该节点的次数
	public int end;		//该节点作为字符串结尾的次数
	public Node[] next;
	
	public Node() {
		pass=0;
		end=0;
		next=new Node[26];
	}
}

贪心算法

在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫作贪心算法。

也就是说,不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解。

局部最优 -?-> 整体最优

贪心算法的在笔试时的解题套路

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

  2. 脑补出贪心策略A、贪心策略B、贪心策略C...

  3. 用解法X和对数器,去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确

  4. 不要去纠结贪心策略的证明

从头到尾展示最正统的贪心策略求解过程

例子:

给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最小的字典序。

(证明贪心策略可能是件非常腌心的事情。平时当然推荐你搞清楚所有的来龙去脉,但是笔试时用对数器的方式!)

    public static class MyComparator implements Comparator<String> {
        @Override
        public int compare(String o1, String o2) {
            return (o1 + o2).compareTo(o2 + o1);
        }
    }
    public static String lowestString(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }
        Arrays.sort(strs, new MyComparator());
        String res = "";
        for (int i = 0; i < strs.length; i++) {
            res += strs[i];
        }
        return res;
    }

贪心策略在实现时,经常使用到的技巧:#

  1. 根据某标准建立一个比较器来排序

  2. 根据某标准建立一个比较器来组成堆

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。#

一群人想整分整块金条,怎么分最省铜板?

例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60。

金条要分成10,20,30三个部分。 如果先把长度60的金条分成10和50,花费60;再把长度50的金条分成20和30,花费50;一共花费110铜板。

但是如果先把长度60的金条分成30和30,花费60;再把长度30金条分成10和20,花费30;一共花费90铜板。

输入一个数组,返回分割的最小代价。

    public static int lastMoney(int[] arr) {
        PriorityQueue<Integer> pQ = new PriorityQueue<>();
        for (int i = 0; i < arr.length; i++) {
            pQ.add(arr[i]);
        }
        int sum = 0;
        int cur = 0;
        while (pQ.size() > 1) {
            cur = pQ.poll() + pQ.poll();
            sum += cur;
            pQ.add(cur);
        }
        return sum;
    }

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。#

给你每一个项目开始的时间和结束的时间(给你一个数组,里面是一个个具体的项目),你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。

返回这个最多的宣讲场次。

    public static class Program {//项目类型
        public int start;
        public int end;

        public Program(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }
    public static class ProgramComparator implements Comparator<Program> {//比较器类型
        @Override
        public int compare(Program o1, Program o2) {
            return o1.end - o2.end;
        }
    }
    public static int bestArrange(Program[] programs, int start) {
        Arrays.sort(programs, new ProgramComparator());
        int result = 0;
        for (int i = 0; i < programs.length; i++) {
            if (start <= programs[i].start) {
                result++;
                start = programs[i].end;
            }
        }
        return result;
    }

输入:

正数数组costs

正数数组profits

正数k

正数m

含义:

costs[i]表示i号项目的花费

profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润)

k表示你只能串行的最多做k个项目

m表示你初始的资金

说明:

你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。

输出:

你最后获得的最大钱数。

    public static class Node {
        public int profit;
        public int cost;
        public Node(int profit, int cost) {
            this.profit = profit;
            this.cost = cost;
        }
    }
    public static class MinCostComparator implements Comparator<Node> {
        @Override
        public int compare(Node o1, Node o2) {
            return o1.cost - o2.cost;
        }
    }
    public static class MaxCostComparator implements Comparator<Node> {
        @Override
        public int compare(Node o1, Node o2) {
            return o2.profit - o1.profit;
        }
    }
    public static int findMaximizedCapital(int k, int W, int[] profits, int[] costs) {
        Node[] nodes = new Node[profits.length];
        for (int i = 0; i < profits.length; i++) {
            nodes[i] = new Node(profits[i], costs[i]);
        }
        PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxCostComparator());
        for (Node node : nodes) {
            minCostQ.add(node);
        }
        for (int i = 0; i < k; i++) {//进行k轮
            while (!minCostQ.isEmpty() && minCostQ.peek().cost <= W) {
                maxProfitQ.add(minCostQ.poll());
            }
            if (maxProfitQ.isEmpty()) {
                return W;
            }
            W += maxProfitQ.poll().profit;
        }
        return W;
    }

一个数据流中,随时可以取得中位数

    public static class MedianHolder {
        private PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new MaxHeapComparator());
        private PriorityQueue<Integer> minHeap = new PriorityQueue<>(new MinHeapComparator());
        private void modifyTwoHeapsSize() {
            if (this.maxHeap.size() == this.minHeap.size() + 2) {
                this.minHeap.add(this.maxHeap.poll());
            }
            if (this.minHeap.size() == this.maxHeap.size() + 2) {
                this.maxHeap.add(this.minHeap.poll());
            }
        }
        public void addNumber(int num) {
            if (maxHeap.isEmpty() || num <= maxHeap.peek()) {
                maxHeap.add(num);
            } else {
                minHeap.add(num);
            }
            modifyTwoHeapsSize();
        }
        public Integer getMedian() {
            int maxHeapSize = this.maxHeap.size();
            int minHeapSize = this.minHeap.size();
            if (maxHeapSize + minHeapSize == 0) {
                return null;
            }
            Integer maxHeapHead = this.maxHeap.peek();
            Integer minHeapHead = this.minHeap.peek();
            if (((maxHeapSize + minHeapSize) & 1) == 0) {
                return (maxHeapHead + minHeapHead) / 2;
            }
            return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
        }
    }
    public static class MaxHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    }
    public static class MinHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }
    }

N皇后问题

N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上。
给定一个整数n,返回n皇后的摆法有多少种
n = 1,返回1
n = 2或3,2皇后和3皇后问题怎么摆都不行,返回0
n = 8,返回92

public static int num1(int n){
	if(n < 0){
		return 0;
	}
	//record[0]代表第0行的皇后放在哪一列
	int[] record = new int[n];
	return process1(0,record,n);
}
//record[0..i-1]的皇后,任意两个皇后一定不共行,不共列,不共斜线
//当前来到第i行,
//record[0...i-1]表示之前的行,放了的皇后位置
//n代表整体一共有多少行
//返回值是,摆完所有的皇后,合理的摆法有多少种
public static int process1(int i,int[] record,int n){
	//终止行
	if(i == n){
		return 1;
	}
	int res = 0;
	//当前行在i行,尝试所有的列j
	for(int j = 0;j < n;j++){
		//当前i行的皇后,放在j列,会不会和之前的(0...i-1)的皇后,共行共列或者共斜线
		//如果是,认为无效
		//如果不是,认为有效
		if(isValid(record,i,j){
			record[i] = j;
			res += process1(i+1,record,n);
		}
	}
	return res;
}
//record[0...i-1]需要看,后续的不需要
//返回i行皇后,放在j列,是否有效
public static boolean isValid(int[] record,int i,int j){
	for(int k = 0; k < i; k++){
		//共列和共斜线返回false
		if(j == record[k] || Math.abs(record[k] - j) == Math.abs(i-k)){
			return false;
		}
	}
	return true;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值