20200723:198周周赛学习记录

198周周赛

    • 第一题:换酒问题
      • 题目
      • 示例
      • 解题思路
      • 代码实现
    • 第二题:子树中标签相同的节点数
      • 题目
      • 解题思路
      • 代码实现
    • 第三题:最多的不重叠子字符串
      • 题目
      • 示例
      • 解题思路
    • 第四题:找到最接近目标值的函数值
      • 题目
      • 示例
      • 解题思路
      • 代码实现

第一题:换酒问题

题目

便利店搞促销,numExchange个空酒瓶可以兑换1瓶新酒,你买了numBottles瓶新酒,求你最多能喝多少酒?

示例

输入:numBottles = 9,numExchange = 3

输出:9+3+1 = 13

解释:9瓶喝完可以兑换三瓶新酒,这三瓶喝完可以再兑换一瓶,因此为13瓶

输入:numBottles = 15,numExchange = 4

输出:15+3+1 = 19

解释:15瓶喝完可以兑换3瓶新酒,这3瓶喝完加上之前剩下的3个空瓶还可以再兑换一瓶,因此为19瓶

解题思路

首先根据上述两个示例的解释你大致可以想到本题需要一个while循环解决问题,当然这无关紧要。结果集为numBottles+可以兑换的酒的数目。可以兑换的酒的数目也就是空瓶的计算,空瓶包含的是前面喝完的空瓶里面没兑换完的和再次兑换来的酒喝完剩下的空瓶之和,因此需要注意这两点即可

代码实现

class Solution{
	public int numWaterBottles(int numBottles,int numExchange) {
		int res = numBottles;
		int tmp = numBottles;
		while ( tmp >= numExchange) {
			tmp -= numExchange;
			res++;
			tmp++;
		}
		return res;
	}
}

第二题:子树中标签相同的节点数

题目

给定一棵树,连通的无环无向图。这棵树的编号从0 - (n-1)的n个节点组成,恰好有n-1条edges,树的根节点为节点0,树上的每一个节点都有一个标签,也就是字符串labels中的一个小写字符(编号为i的节点的标签就是labels[i])。

边数组由edges给出以edges[i] = [a,b]的形式给出,该格式表示节点a和b之间存在一条边。

返回一个大小为n的数组,其中ans[i] 表示第i个节点的子树中与节点i标签相同的节点数。

解题思路

本题的思路其实是很明确的,首先题目给出了edges二维数组,也就是说你可以利用其构图存图,将此图存入map或者TreeSet中,然后就直接dfs即可完成对子树中相同标签节点数的求值,注意书写细节。此处在阅读大神的代码时找到一种更加先进的构图方法,或者说,用TreeSet构图更加的方便也更加容易理解。

代码实现

class Solution {
    public int[] countSubTrees(int n,int[][] edges,String labels) {
        // 存图,此为list存图法,下列一种TreeSet存图法更加容易理解。
        /*
        	
        */
        Map<Integer,List<Integer>> edgesMap = new HashMap<Integer,List<Integer>>();
        for (int[] edge : edges) {
            int node0 = edge[0],node1 = edge[1];
            List<Integer> list0 = edgesMap.getOrDefault(node0,new ArrayList<Integer>());
            List<Integer> list1 = edgesMap.getOrDefault(node1,new ArrayList<Integer>());
            list0.add(node1);
            list1.add(node0);
            edgesMap.put(node0,list0);
            edgesMap.put(node1,list1);
        }
       	// dfs
        int[] counts = new int[n];
        boolean[] visited = new boolean[n];
        dfs(0,counts,visited,edgesMap,labels);
        return counts;
    }
    
    public int[] dfs(int node,int[] counts,boolean[] visited,Map<Integer,List<Integer>> edgesMap,String labels) {
        visited[node] = true;
        int[] curCounts = new int[26];
        curCounts[labels.charAt(node) - 'a']++;
        List<Integer> nodesList = edgesMap.get(node);
        for (int nextNode:nodesList) {
            if (!visited[nextNode]) {
                int[] childCounts = dfs(nextNode,counts,visited,edgesMap,labels);
                for (int i = 0; i < 26; i++) {
                    curCounts[i] += childCounts[i];
                }
            }
        }
        counts[node] = curCounts[labels.charAt(node) - 'a'];
        return curCounts;
    }
}

第三题:最多的不重叠子字符串

题目

给定只包含小写字母的字符串s,你需要找到s中最多数目非空子字符串:

  1. 子字符串之间互不重叠
  2. 如果一个子字符串包含c,那么就应该包含s中所有的c

返回字符串总长度最小的组合,可以按任意顺序返回。

示例

​ 输入:s = “adefaddaccc”

​ 输出:[“e”,“f”,“ccc”]

​ 输入:s = “abbaccd”

​ 输出:[“d”,“bb”,“cc”]

解题思路

对于本题而言,你大致可以想到需要一个贪心的解法,

思路为:

​ 首先枚举所有满足第2个条件且有可能加入结果集的子串,最多26个,然后按照子串的长度暴力加贪心,选择不重叠的子串加入到结果集。

代码如下:

class Solution {
    public List<String> maxNumOfSubstrings (String s) {
        List<int[]> res = getCandidateIntervals(s);
        // 重写sort函数,获取最终结果
        Collections.sort(res,(o1,o2) -> (o1[1] - o1[0] - o2[1] + o2[0]));
        for (int i = 0; i < res.size(); i++) {
            for (int j = i - 1; j >= 0; j--) {
                if (!(res.get(i)[0] > res.get(j)[1] || res.get(i)[1] < res.get(j)[0])) {
                    res.remove(i--);
                    break;
                }
            }
        }
        return res.stream().map(o -> (s.substring(o[0],o[1] + 1))).collect(Collectors.toList());
    }
    
    // 获取满足第二个条件的所有子字符串
    private List<int[]> getCandidateIntervals (String s) {
        char[] chs = s.toCharArray();
        int[] end = new int[26];
        for (int i = 0; i < s.length(); i++) {
            end[chs[i] - 'a'] = i;
        }
        List<int[]> res = new ArrayList<>();
        boolean[] vis = new boolean[26];
        /*
        	输入:s = "adefaddaccc"
			输出:["e","f","ccc"]
			
			输入:s = "abbaccd"
			输出:["d","bb","cc"]
        */
        for (int i = 0; i < s.length(); i++) {
            if (!vis[chs[i] - 'a']) {
                int last = end[chs[i] - 'a'];
                for (int j = i + 1; j <= last; j++) {
                    if (vis[chs[j] - 'a']) {
                        last = -1;
                        break;
                    }
                    last = Math.max(last,end[chs[j] - 'a']);
                }
                vis[chs[i] - 'a'] = true;
                if (last != -1) {
                    res.add(new int[] {i,last});
                }
            }
        }
        return res;
    }
}

第四题:找到最接近目标值的函数值

题目

Winston构造了一个如下所示的函数

func(arr,l,r) {
	if (r < l) {
		return -1000000000
	}
	ans = arr[l]
	for (i = l + 1; i <= r; i++) {
		ans = ans & arr[i]
	}
	return ans
}

他有一个整数数组arr和一个整数target,他想找到能够让 | func(arr,l,r) - target|最小的值。请你返回这个值。

示例

输入:arr = [9,12,3,7,15] target = 5

输出 : 2

解释: Winston的func函数得到的所有可能的l和r的组合为

{

​ [0,0],[1,1],[2,2],[3,3],[4,4],

​ [0,1],[1,2],[2,3],[3,4],

​ [0,2],[1,3],

​ [2,4],[0,3],

​ [1,4],[0,4]

}

Winston的func结果为[9,12,3,7,15,8,0,3,7,0,0,3,0,0,0]

最接近5的为7和3,因此返回最小差值为2

解题思路

不会做,纯照抄的zerotrac大佬的C++代码复制成了我的java代码

抄完理解完之后的马后炮解题思路如下:

题目意思都懂,但是就是菜,func给的意思是计算arr[l]到arr[r]的按位与之和。然后让你算出这些按位与之和里面和target最接近的那个数与target的差值的绝对值。

按位与的两个性质为:

  1. 按位与的值是递减的,因为a & b的结果 >= max(a,b),这是可以根据二进制的计算想到的,与之后原来为1的只能变为0,而原来为0的不可能变为1。
  2. 题目给定的arr[r] <= 106,也就是意味着在二进制中106 <2^20次方,意思是最多有20种结果,最多有20个1变成了0,而0是不可能变为1的,所以func的结果集ans最多有20种结果。

由此思路就清楚了,

  1. 先从小到大遍历r,用一个集合维护func的结果ans的值,最多20个,所以直接初始化集合的大小为20,
  2. 遍历到r+1的时候,新的值为原来集合的每个值和arr[r+1]进行按位与的结果,再在集合里加一个arr[r+1]就行了,然后进行去重,就是arr[r+1]的结果。
  3. 最后再找出最小差值即可。

代码实现

class Solution{
	public int closestToTarget(int[] arr,int target) {
        // 结果set
		HashSet<Integer> set= new HashSet<>();
        int min = Integer.MAX_VALUE;
        // 遍历arr,
        for (int i : arr) {
            HashSet<Integer> cur2 = new HashSet<>();
            set1.add(i);
            min = Math.min(min,Math.abs(target - i));
            for (int j : set) {
                int val = j & i;
                set1.add(val);
                min = Math.min(min,Math.abs(target - val));
            }
            set= set1;
 		}
        return min;
	}
}

你可能感兴趣的:(leetcode学习记录篇)