左神数据结构与算法(中级提升)——05

题目四十一:最大活动奖励

CC直播的运营部门组织了很多运营活动,每个活动需要花费一定的时间参与,主播每参加完一个活动即可得到一定的奖励,参与活动可以从任意活动开始,但一旦开始,就需要将后续活动参加完毕(注意:最后一个活动必须参加),活动之间存在一定的依赖关系(不存在环的情况),现在给出所有的活动时间与依赖关系,以及给出有限的时间,请帮助主播计算在有限的时间内,能获得的最大奖励,以及需要的最少时长。

package class07;

import org.junit.Test;

import java.util.*;

public class Code07_MaxActivityMoney {

    //根据已知的信息,设置的节点信息内容有哪些
    class Node{
        int day;
        int money;
        LinkedList<Node> preNodes;  //指向该Node节点的上一级节点集合
        TreeMap<Integer,Integer> treeMap; //从最后一个活动到该节点活动的汇总

        public Node(int day,int money){
            this.day = day;
            this.money = money;
            preNodes = new LinkedList<>();
            treeMap = new TreeMap<>();
        }

        public void put_treeMap(int day,int money){
            treeMap.put(day,money);
            adjust();
        }

        public void adjust(){
            if (preNodes.isEmpty()){  //当没有上级节点时候,需要对节点信息汇总进行调整,天数增加,钱也增加,不满足删除
                if (!treeMap.isEmpty()){
                    int preMoney = Integer.MIN_VALUE;
                    //在遍历Map过程中,不能用map.put(key,newVal),map.remove(key)来修改和删除元素,会引发 并发修改异常,
                    // 可以通过迭代器的remove():从迭代器指向的 collection 中移除当前迭代元素来达到删除访问中的元素的目的。
//                    for (Integer i : treeMap.keySet()) {  //删除的时候出现问题 ConcurrentModificationException
//                        if (treeMap.get(i) <= preMoney){
//                            treeMap.remove(i);
//                            continue;
//                        }
//                        preMoney = treeMap.get(i);
//                    }
                    Iterator<Map.Entry<Integer, Integer>> iterator = treeMap.entrySet().iterator();
                    while (iterator.hasNext()){
                        Map.Entry<Integer, Integer> i = iterator.next();
                        Integer money = i.getValue();
                        if (money < preMoney){
                            iterator.remove();
                            continue;
                        }
                        preMoney = money;
                    }
                }
            }
        }
    }

    //根据已知的信息,设置节点并返回最后一个节点
    //dayMoney  节点的信息     arr 节点的依赖关系
    public Node InfoChangeToNode(int[][] dayMoney,int[][] arr){
        HashMap<Integer,Node> hashMap = new HashMap<>();
        for (int i = 0;i < dayMoney.length;i++){
            Node node = new Node(dayMoney[i][0],dayMoney[i][1]);
            hashMap.put(i,node);
        }
        for (int i = 1;i < arr.length;i++){
            for (int j = 0;j < i;j++){
                if (arr[j][i] == 1){
                    Node node = hashMap.get(i);
                    Node preNode = hashMap.get(j);
                    node.preNodes.add(preNode);
                }
            }
        }
        return hashMap.get(arr.length - 1);  //返回最后一个节点
    }

    //根据最后一个节点进行反向深度优先遍历,每一个节点的treeMap进行设置
    public void set(Node node){
        setDayAndMoney(node,0,0);
    }

    public void setDayAndMoney(Node node,int preDay,int preMoney){
        int day = node.day + preDay;
        int money = node.money + preMoney;
        node.put_treeMap(day,money);  //当前节点的treeMap信息添加
        for (Node preNode : node.preNodes) {
            setDayAndMoney(preNode,day,money);
        }
    }

    //主方法:获得活动获得的最大奖励
    public int[] getMaxMoney(int limitDay,int[][] dayMoney,int[][] arr) {
        if (dayMoney.length != arr.length || limitDay <= 0) {
            throw new RuntimeException("信息有误");
        }
        Node endNode = InfoChangeToNode(dayMoney, arr);
        set(endNode);
        //这样每个节点的treeMap就有信息
        Node head = new Node(0, 0);
        head.treeMap.put(endNode.day, endNode.money);//把尾节点的信息加入
        //深度优先遍历 dfs
        Stack<Node> stack = new Stack<>();
        HashSet<Node> hashSet = new HashSet<>();  //防止重复
        Node cur = endNode;
        stack.push(cur);
        hashSet.add(cur);
        while (!stack.isEmpty()) {
            Node temp = stack.pop();
            for (Node preNode : temp.preNodes) {
                if (!hashSet.contains(preNode)) {
                    stack.push(temp);
                    stack.push(preNode);
                    hashSet.add(preNode);
                    //对节点信息进行处理
                    TreeMap<Integer, Integer> preTreeMap = preNode.treeMap;
                    for (Integer i : preTreeMap.keySet()) {
                        head.put_treeMap(i, preTreeMap.get(i));
                    }
                    break;
                }
            }
        }
        for (Integer i : head.treeMap.keySet()) {
            System.out.println("day: " + i + ",money: " + head.treeMap.get(i));
        }

        //根据限制天数limitDay选择最大的活动奖励
        int[] res = new int[2];
        if (head.treeMap.firstKey() > limitDay) {
            res[0] = 0;
            res[1] = 0;
        } else if (head.treeMap.lastKey() < limitDay) {
            res[0] = head.treeMap.get(head.treeMap.lastKey());
            res[1] = head.treeMap.lastKey();
        }else{
            if (head.treeMap.containsKey(limitDay)) {
                res[0] = head.treeMap.get(limitDay);
                res[1] = limitDay;
            } else {
                //limitDay介于最小天数和最大天数之间,且不等于边界
                while (!head.treeMap.containsKey(--limitDay)) {} //一直向下找,找到最接近limitDay的那一组数据
                res[0] = head.treeMap.get(limitDay);
                res[1] = limitDay;
            }
        }
        return res;
    }

    @Test
    public void test(){
        int[][] dayMoney = {
                {3,2000},{3,4000},{2,2500},{1,1600},{4,3800},{2,2600},{4,4000},{3,3500}
        };
        int[][] arr = {
                {0,1,1,0,0,0,0,0},
                {0,0,0,1,1,0,0,0},
                {0,0,0,1,0,0,0,0},
                {0,0,0,0,1,1,1,0},
                {0,0,0,0,0,0,0,1},
                {0,0,0,0,0,0,0,1},
                {0,0,0,0,0,0,0,1},
                {0,0,0,0,0,0,0,0},
        };
        int[] res = getMaxMoney(20,dayMoney,arr);
        System.out.println(Arrays.toString(res));
    }
}

题目四十二:表达式组合方式

给定一个只由0(假)、1(真)、&(逻辑与)、|(逻辑或)和^(异或)五种字符组成的字符串express,再给定一个布尔值desired。返回express能有多少种组合方式,可以达到desired的结果。

eg:express = ”1^0|0|1”,desired=false

只有1^((0|0)|1)和1^(0|(0|1))的组合可以得到false,返回2。

express = “1”,desired = false

无组合可以得到false,返回0.

package class08;

public class Code01_Expression {

    //判断输入的expression是不是有效的字符
    public boolean isVaild(char[] exp){
        if((exp.length & 1) == 0){
            return false;
        }
        for (int i = 0;i < exp.length;i = i + 2){
            if ((exp[i] != '1') && (exp[i] != '0')){
                return false;
            }
        }
        for (int i = 1;i < exp.length;i = i + 2){
            if((exp[i] != '|') && (exp[i] != '&') && (exp[i] != '^')){
                return false;
            }
        }
        return true;
    }

    public int num1(String express,boolean desired){
        if (express == null || express.equals("")){
            return 0;
        }
        char[] chars = express.toCharArray();
        if(!isVaild(chars)){
            return 0;
        }
        return p(chars,desired,0,chars.length - 1);
    }

    //L R 不要压中逻辑符号
    public int p(char[] exp,boolean desired,int L,int R){
        if (L == R){
            if(exp[L] == '1'){
                return desired ? 1 : 0;
            }else {
                return desired ? 0 : 1;
            }
        }

        int res = 0;
        if (desired){ //结果为true
            //i 位置上尝试 L ... R 范围上的每一个逻辑符号,都是最后结合的
            for (int i = L + 1;i < R;i += 2){
                switch (exp[i]){
                    case '&':
                        res += p(exp,true,L,i - 1) * p(exp,true,i + 1,R);
                        break;
                    case '|':
                        res += p(exp,true,L,i - 1) * p(exp,false,i + 1,R);
                        res += p(exp,false,L,i - 1) * p(exp,true,i + 1,R);
                        res += p(exp,true,L,i - 1) * p(exp,true,i + 1,R);
                        break;
                    case '^':
                        res += p(exp,true,L,i - 1) * p(exp,false,i + 1,R);
                        res += p(exp,false,L,i - 1) * p(exp,true,i + 1,R);
                        break;
                }
            }
        }else { //结果为false
            for (int i = L + 1;i < R;i += 2){
                switch (exp[i]){
                    case '&':
                        res += p(exp,true,L,i - 1) * p(exp,false,i + 1,R);
                        res += p(exp,false,L,i - 1) * p(exp,true,i + 1,R);
                        res += p(exp,false,L,i - 1) * p(exp,false,i + 1,R);
                        break;
                    case '|':
                        res += p(exp,false,L,i - 1) * p(exp,false,i + 1,R);
                        break;
                    case '^':
                        res += p(exp,true,L,i - 1) * p(exp,true,i + 1,R);
                        res += p(exp,false,L,i - 1) * p(exp,false,i + 1,R);
                        break;
                }
            }
        }
        return res;
    }
}


题目四十三:子字符串最长

在一个字符串中找到没有重复字符字串中最长的长度

eg:abcabcbb没有重复字符的最长字串是abc,长度为3

bbbbb 答案是b,长度为1

pwwkew,答案是wke,长度为3

要求:答案必须是字串,”pwke”是一个子字符序列但不是一个子字符串

package class08;

public class Code02_MaxUniqueSubList {
    
    public int maxUnique(String s){
        if (s == null || s.equals("")){
            return 0;
        }
        char[] chars = s.toCharArray();
        //map 替代哈希表,假设字符的码是 0~255
        int[] map = new int[256];  //记录字符出现的位置
        for (int i = 0;i < 256;i++){
            map[i] = -1;
        }
        int len = 0;
        int pre = -1;
        int cur = 0;
        for (int i = 0;i != chars.length;i++){
            pre = Math.max(pre,map[chars[i]]);
            cur = i - pre;
            len = Math.max(len,cur);
            map[chars[i]] = i;
        }
        return len;
    }
}

题目四十四:编辑代价

给定两个字符串str1和str2,再给定三个整数ic,dc和rc,分别代表插入、删除、和替换一个字符的代价,返回str1编辑成str2的最小代价

eg:str1=”abc” str2=”adc”  ic=5,dc=3,rc=2

从”abc”编辑成”adc”,把’b’替换成’d’是代价最小的,所以返回2

str1=”abc” str2=”adc” ic=5,dc=3,rc=100

从”abc”编辑成”adc”,先删除’b’,再插入’d’是代价最小的,所以返回8

str1=”abc” str2=”abc” ic=5,dc=3,rc=2

不用编辑了,本来就是一样的字符串,所以返回0

package class08;

import java.util.Locale;

public class Code03_EditCost {

    public int minCost(String str1,String str2,int ic,int dc,int rc){
        if(str1 == null || str2 == null){
            return 0;
        }
        char[] chars1 = str1.toCharArray();
        char[] chars2 = str2.toCharArray();
        int row = chars1.length + 1;
        int col = chars2.length + 1;
        int[][] dp = new int[row][col];
        for (int i = 1;i < row;i++){
            dp[i][0] = dc * i;
        }
        for (int i = 1;i < col;i++){
            dp[0][i] = ic * i;
        }
        for (int i = 1;i < row;i++){
            for (int j = 1;j < col;j++){
                if(chars1[i - 1] == chars2[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1];
                }else {
                    dp[i][j] = dp[i - 1][j - 1] + rc;
                }
                dp[i][j] = Math.min(dp[i][j],dp[i][j - 1] + ic);
                dp[i][j] = Math.min(dp[i][j],dp[i - 1][j] + dc);
            }
        }
        return dp[row - 1][col - 1];
    }
}

题目四十五:无重复的最小字典序子字符串

给定一个全是小写字母的字符串str,删除多余字符,使得每种字符只保留一个,并让最终字符串的字典序最小

eg:str=”acbc” ,删掉第一个’c’,得到”abc”,是所有结果字符串中字典序最小的

str=”dbcacbca”,删掉第一个’b’,第一个’c’,第二个’c’,第二个’a’,得到”dabc” 是所有结果字符串中字典序最小的

package class08;

public class Code04_RemoveDuplicate {
    
    public String remove(String str){
        if(str == null || str.length() < 2){
            return str;
        }
        int[] map = new int[256];
        for (int i = 0; i < str.length();i++){
            map[str.charAt(i)]++;
        }
        int minASCIndex = 0;
        for (int i = 0;i < str.length();i++){
            if (--map[str.charAt(i)] == 0){
                break;
            }else {
                //获得最小的字符的第一个位置
                minASCIndex = str.charAt(minASCIndex) > str.charAt(i) ? i : minASCIndex;
            }
        }
        return String.valueOf(str.charAt(minASCIndex) + 
                remove(
                        str.substring(minASCIndex+1)
                                .replaceAll(String.valueOf(str.charAt(minASCIndex)),"")
                )
        );
    }
}

题目四十六:数据编码

再数据加密和数据压缩中常需要对特殊的字符串进行编码。给定的字母表A由26个小写英文字母组成,即A={a,b,c..z}。该字母表产生的长序字符串是指定字符串中字母从左到右出现的次数与字母表中出现的次序相同,且每个字符最多出现一次。

eg:a,b,ab,bc,xyz等字符串是升序字符串。对字母表A产生的所有长度不超过6的升序字符串按照字典排列编码如下:a(1),b(2),c(3)……,z(26),ab(27),ac(28)……对于任意长度不超过16的升序字符串,迅速计算出它在上述字典中的编码。

输入描述:

第一行是一个正整数N,表示接下来共有N行,在接下来的N行中,每行给出一个字符串。

输出描述:

输出N行,每行对应于一个字符串编码。

eg:输入

3

a

b

ab

输出:

1

2

27

package class08;

public class Code05_StringCoding {

    //以i 字符开始的,总长度为len的子序列有多少个
    public int g(int i,int len){
        int sum = 0;
        if (len == 1){
            return 1;
        }
        for (int j = i + 1;j <= 26;j++){
            sum += g(j,len - 1);
        }
        return sum;
    }
    
    //长度为len的字符串有多少个
    public int f(int len){
        int sum = 0;
        for (int i = 1;i <= 26;i++){
            sum += g(i,len);
        }
        return sum;
    }
    
    public int kth(String str){
        char[] chars = str.toCharArray();
        int sum = 0;
        int len = chars.length;
        for (int i = 1;i < len;i++){  //长度len之前的总个数
            sum += f(i);
        }
        int first = chars[0] - 'a' + 1;  //第一个字符
        for(int i = 1;i < first;i++){
            sum += g(i,len);
        }
        int pre = first;
        for (int i = 1;i < len;i++){
            int cur = chars[i] - 'a' + 1;
            for(int j = pre + 1;j < cur;j++){
                sum += g(j,len - i);
            }
            pre = cur;
        }
        return sum + 1;
    }
}

题目四十七:基本计算器(leetCode224)

//每次计算,遇到括号,就把之前的结果压入栈,再压入括号之前的符号,
//括号结束,出栈,把 之前的结果 和 当前计算的结果 * 符号 加起来
public int calculate2(String s) {
    Stack<Integer> stack = new Stack<Integer>();

    int sign = 1;  // sign 代表正负
    int res = 0;  //存放结果
    int i = 0;//索引
    while (i < s.length()){
        if (s.charAt(i) == ' '){
            i++;
        }else if(s.charAt(i) == '+'){
            sign = 1;
            i++;
        }else if (s.charAt(i) == '-'){
            sign = -1;
            i++;
        }else if(s.charAt(i) == '('){
            stack.push(res);
            res = 0;
            stack.push(sign);
            sign = 1;
            i++;
        }else if (s.charAt(i) == ')'){
            res = stack.pop() * res + stack.pop(); //前一个为符号数,后一个为括号之前的结果
            i++;
        }else {
            int num = s.charAt(i) - '0';
            i++;
            while (i < s.length() && s.charAt(i) >= '0' && s.charAt(i) <= '9'){
                num = num * 10 + (s.charAt(i) - '0');
                i++;
            }
            res += sign * num;
        }
    }
    return res;
}

题目四十九:括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合

输入:n = 3

输出:["((()))","(()())","(())()","()(())","()()()"]

List<String> list;

   public List<String> generateParenthesis(int n) {
   this.list = new ArrayList<>();
   process("",n,n);
   return list;
   }

//str : 递归过程中的字符串 每次递归都相当于是新建一个
//left : 左括号个数
//right: 右括号个数
public void process(String s,int left,int right){
   if (left == 0 && right == 0){//当剩余的左括号个数和右括号个数都没了
      list.add(s);
      return;
   }

   //左括号个数和右括号个数一样,下一次只能使用左括号
   if (left == right){
      process(s+"(",left - 1,right);
   }else if (left < right){
      //剩下的可以先加左括号也可以先加右括号
      if (left > 0) { //左括号个数不能小于0 ,否则会一直进行
         process(s + "(", left - 1, right);
      }
      if (right > 0) {
         process(s + ")", left, right - 1);
      }
   }
}

题目五十:火柴拼正方形

你将得到一个整数数组 matchsticks ,其中 matchsticks[i] 是第 i 个火柴棒的长度。你要用 所有的火柴棍 拼成一个正方形。你 不能折断 任何一根火柴棒,但你可以把它们连在一起,而且每根火柴棒必须 使用一次 。

如果你能使这个正方形,则返回 true ,否则返回 false 。

输入: matchsticks = [1,1,2,2,2] 输出: true

输入: matchsticks = [3,3,3,3,4] 输出: false

public boolean makesquare(int[] matchsticks) {
   if (matchsticks.length < 4){
      return false;
   }
   //先对数组进行求和,对四进行取余,若不是整数,则无法拼成
   int sum = 0;
   for (int i : matchsticks){
      sum += i;
   }
   if (sum % 4 != 0){
      return false;
   }
   //边长为整数,进行拼凑
   int length = sum / 4;
   int[] sides = new int[4]; //四条边长,每个位置存放对应数字的和
   //先加入大的边,会降低时间
   Arrays.sort(matchsticks);
   return process(matchsticks,matchsticks.length - 1,sides,length);
}

/**
 * 返回能否拼成一个正方形
 * @param matchsticks  整数数组,存放整个数的
 * @param index    当前遍历到哪一个数,放到随机的一条边
 * @param sides    四条边长
 * @param length   边长
 * @return
 */
public boolean process(int[] matchsticks,int index,int[] sides,int length){
   if (index == -1){
      return length == sides[0] && length == sides[1]
            && length == sides[2] && length == sides[3];
   }
   //当前的这个数随机放在四条边的哪一条边中
   for (int i = 0;i < sides.length;i++){
      //当前边无法加入数时,加入下一条边
      // side[i]只有在side[i-1] <= length 的时候才会往side[i]中加
      if (sides[i] + matchsticks[index] > length
            || (i > 0 && sides[i] == sides[i-1])) {
         continue;
      }
      sides[i] += matchsticks[index];
      if (process(matchsticks, index - 1, sides, length)) {
         return true;
      }
      //回溯
      sides[i] -= matchsticks[index];

   }
   return false;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值