算法进修Day-15

算法进修Day-15

29. 两数相除

难度:中等
题目要求:
给定两个整数,被除数dividend除数divisor。将两数相除,要求不使用乘法、除法和取余运算。
整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345将被截断为8-2.7335将被截断至-2
返回被除数dividend除以除数divisor

示例1

输入:dividend = 10, divisor = 3
输出:3

示例2

输入:dividend = 7, divisor = -3
输出:-2

题解

一开始想不到怎么做,看了别人的代码才了解到需要用到位运算:

首先,判断以下三种情况:

  • 如果 dividend = 0,那么直接返回0
  • 如果 divisor = 1,那么直接返回被除数本身
  • 如果 dividend = − 2 31 -2^{31} 231 并且 divisor = -1,那么商是 2 31 2^{31} 231,超出有符号整数范围,所以返回 2 31 − 1 2^{31} - 1 2311

其余的情况,首先判断符号的正负,之后将被除数和除数都变成负数,计算商的绝对值。因为不能够使用乘除,所以要用位运算代替
首先将除数左移,使得在除数的绝对值不超过被除数的绝对值的情况下,将除数的绝对值最大化。假设除数左移了 k 位之后变成currDivisor,则currDivisor = divisor x 2 k 2^{k} 2kcurrDivisor除以divisor的商是 2 k 2^k 2k,将dividend的值减去currDivisor,然后对剩余的dividend继续计算商

dividend的值减去currDivisor之后,如果dividend的绝对值小于currDivisor的绝对值,就需要将currDivisor右移,直到dividend的绝对值大于currDivisor的绝对值

重复计算商的操作,计算两数相除的结果,当dividend的绝对值小于divisor的绝对值时,商的剩余部分是0,到这里,计算结束,得到两数相除的结果的绝对值,之后根据商的正负来的到最终结果并返回

想法代码

public static int Divide(int dividend, int divisor)
    {
        if (dividend == 0)
        {
            return 0;
        }
        if (divisor == 1)
        {
            return dividend;
        }
        if (dividend == int.MinValue && divisor == -1)
        {
            return int.MaxValue;
        }
        bool negative = dividend < 0 ^ divisor < 0;
        dividend = dividend != int.MinValue ? -Math.Abs(dividend) : dividend;
        divisor = divisor != int.MinValue ? -Math.Abs(divisor) : divisor;
        int quotient = 0;
        int currDiviser = divisor, factor = 1;
        while (dividend >> 1 < currDiviser)
        {
            currDiviser <<= 1;
            factor <<= 1;
        }
        while (dividend <= divisor)
        {
            while (dividend > currDiviser)
            {
                currDiviser >>= 1;
                factor >>= 1;
            }

            quotient += factor;
            dividend -= currDiviser;
        }
        if (negative)
        {
            quotient = -quotient;
        }
        return quotient;
    }

30. 串联所有单词的子串

难度:困难
题目要求:
给定一个字符串s和一个字符串数组wordswords中所有字符串长度相同
s串联子串是指一个包含words中所有字符串以任意顺序排列连接起来的子串。
返回所有串联子串在s中的开始索引。可以以任意顺序返回答案。

示例1

输入:s = “barfoothefoobarman”, words = [“foo”,“bar”]
输出:[0,9]

示例2

输入:s = “wordgoodgoodgoodbestword”, words = [“word”,“good”,“best”,“word”]
输出:[]

示例3

输入:s = “barfoofoobarthefoobarman”, words = [“bar”,“foo”,“the”]
输出:[6,9,12]

题解

采用滑动窗口法,用sLength表示字符串s的长度,用wordLength表示数组words中的每个单词的长度,用wordCount表示数组words的长度即数组中的单词个数,对于由数组words中的单词串联形成的子串,其长度为wordLenth X wordCount,因此可以使用定长滑动窗口寻找串联所有单词的子串,滑动窗口的大小为 w i d o w s = w o r d L e n g t h ∗ w o r d C o u n t widows = wordLength * wordCount widows=wordLengthwordCount

对于降低时间复杂度,应将滑动窗口看成wordCount个单词,每个单词的长度是wordLength。每次将滑动窗口向右移动wordLength位,则有一个单词移出滑动窗口,有一个单词移入滑动窗口,滑动窗口中的其余单词出现的次数不变

i > = w o r d L e n g t h i >= wordLength i>=wordLength时,以下标i作为开始的滑动窗口都可以通过以下标 i − w o r d L e n g t h i - wordLength iwordLength作为开始的窗口滑动依次得到,所以,只需要考虑首个滑动窗口的开始下标位于范围 [ 0 , w o r d L e n g t h − 1 ] [0,wordLength - 1] [0,wordLength1]中的情况。由于范围 [ 0 , w o r d L e n g t h − 1 ] [0,wordLength - 1] [0,wordLength1]中的每个下标都可能做为首个滑动窗口的下标,因此需要分别考虑wordLength个开始下标

0 < = i < w o r d L e n g t h 0 <= i < wordLength 0<=i<wordLength时,以下标i作为开始的滑动窗口,其下标范围是 [ i , i + w i n d o w − 1 ] [i,i + window - 1] [i,i+window1],如果 i + w i n d o w − 1 > s L e n g t h i + window - 1 > sLength i+window1>sLength那么就不存在以下标i作为开始的滑动窗口

一个滑动窗口中的子串是串联所有单词的子串,等价于每个单词在滑动窗口中的出现次数和在数组words中的出现次数相等,因此需要使用哈希表记录每个单词在滑动窗口中的出现次数和在数组words中的出现次数之差,出现次数之差等于0表示出现次数相等

对于 0 < = i < w o r d L e n g t h 0 <= i < wordLength 0<=i<wordLength的每个下标i,执行以下操作:

  • 创建哈希表记录每个单词在滑动窗口中的出现次数和在数组words中的出现次数之差。将数组words中的每个单词在哈希表中的出现次数减1
  • [ s t a r t , e n d ] [start,end] [start,end]表示滑动窗口的下标范围,初始时start = iend = i + window - 1。对于下标 [ i , i + w i n d o w − 1 ] [i,i + window - 1] [i,i+window1]的滑动窗口,将其中的每个单词的出现次数加1,如果哈希表中的每个单词的出现次数之差都是0,则将下标i添加到答案中
  • e n d + w o r d L e n g t h + 1 < s L e n g t h end + wordLength + 1 < sLength end+wordLength+1<sLength时,将startend各向右移动wordLength位,使用移出滑动窗口的单词和移入滑动窗口的单词更新哈希表。如果哈希表中的每个单词出现次数之差都是0,则将下标start添加到答案中

遍历所有下标i之后,就可以得到每个串联子串的开始下标

想法代码

public static IList FindSubstring(string s, string[] words)
    {
        IList substrings = new List();
        int sLength = s.Length, wordLength = words[0].Length, wordCount = words.Length;
        int window = wordLength * wordCount;
        for (int i = 0; i < wordLength && i + window <= sLength; i++)
        {
            IDictionary counts = new Dictionary();
            foreach (string word in words)
            {
                counts.TryAdd(word, 0);
                counts[word]--;
            }
            int start = i, end = i + window - 1;
            for (int j = start; j <= end; j += wordLength)
            {
                string word = s.Substring(j, wordLength);
                counts.TryAdd(word, 0);
                counts[word]++;
                if (counts[word] == 0)
                {
                    counts.Remove(word);
                }
            }
            if (counts.Count == 0)
            {
                substrings.Add(start);
            }
            while (end + wordLength + 1 <= sLength)
            {
                start += wordLength;
                end += wordLength;
                string prev = s.Substring(start - wordLength, wordLength);
                string next = s.Substring(end - wordLength + 1, wordLength);
                counts.TryAdd(prev, 0);
                counts[prev]--;
                if (counts[prev] == 0)
                {
                    counts.Remove(prev);
                }
                counts.TryAdd(next, 0);
                counts[next]++;
                if (counts[next] == 0)
                {
                    counts.Remove(next);
                }
                if (counts.Count == 0)
                {
                    substrings.Add(start);
                }
            }
        }
        return substrings;
    }

你可能感兴趣的:(算法进修,算法,leetcode,c#)