|
| 1 | + |
| 2 | + |
| 3 | +**最近发现一个二分查找很好用的模板,花了一点时间理解了下这个模板,然后这两天就会一直去找二分查找的题,利用那套模板来实现。这套模板和传统模板的不同在于传统的模板,可能我们会这样写:** |
| 4 | + |
| 5 | +``` |
| 6 | +
|
| 7 | +function binarySerach($data, $res) |
| 8 | +{ |
| 9 | + $left = 0; |
| 10 | + $right = count($data) - 1; |
| 11 | + while ($left <= $right) { |
| 12 | + $middle = $left + (($right - $left) >> 1); |
| 13 | + if ($data[$middle] == $res) return $middle; |
| 14 | + else if ($data[$middle] > $res) $right = $middle - 1; |
| 15 | + else $left = $middle + 1; |
| 16 | + } |
| 17 | + return -1; |
| 18 | + } |
| 19 | +
|
| 20 | +``` |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +**这样写的缺点在哪呢,有两个点,一个是while里面产生了三个分支,其实大部分情况下,我们应该只需要去区分一下是否需要排除掉中位数,至于结果的处理,应该留到最后具体的场景实现。第二点就是(这里看不出来,不过我可以举个场景),比如结果不存在,那么返回第一个大于或者第一个小于这类的场景。这时候因为你while的条件是小于或者等于,最后如果没有符合条件,需要格外返回别的数据以满足题目需求,这时候你必然要根据当前的语境去判断是返回left还是right,可能有些时候还会把自己绕晕,如果场景复杂的话。** |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +**下面我们来稍微改进一下当前模板来做下一道题目。** |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | +<a href="https://github.com/wuqinqiang/"> |
| 34 | + <img src="https://github.com/wuqinqiang/Lettcode-php/blob/master/images/287.png"> |
| 35 | +</a> |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +#### 题目描述 |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | +**题目让我们找出重复的数,给定的数组的值都在1-n之间,数组总数是n+1,那么必然有数字是重复的,让我们来找出这个重复的数。** |
| 44 | + |
| 45 | + |
| 46 | + |
| 47 | +#### 题目分析 |
| 48 | + |
| 49 | + |
| 50 | +**这道题可以有更好的解法(我这里强行把它当做二分的场景),二分的解题思路就是取n的中位数,遍历数组,统计不大于中位数的个数,如果不大于中位数的个数比中位数还大,说明重复的数在中位数的左边(注意包括中位数自己,理解一下这一句),否则的话重复的数肯定出现在中位数的右边,而且肯定不包括中位数。好了下面看代码再细讲。** |
| 51 | + |
| 52 | + |
| 53 | + |
| 54 | +#### 代码实现 |
| 55 | + |
| 56 | +``` |
| 57 | + /** |
| 58 | + * @param Integer[] $nums |
| 59 | + * @return Integer |
| 60 | + */ |
| 61 | + function findDuplicate($nums) { |
| 62 | + $left = 1; |
| 63 | + $right = count($nums) - 1; |
| 64 | + while ($left < $right) { // < |
| 65 | + $middle = ($left + $right) >> 1; |
| 66 | + $sum = 0; |
| 67 | + for ($j = 0; $j < count($nums); $j++) { |
| 68 | + if ($nums[$j] <= $middle) { |
| 69 | + $sum++; |
| 70 | + } |
| 71 | + } |
| 72 | + if ($sum <= $middle) { //排除掉中位数 |
| 73 | + $left = $middle + 1; |
| 74 | + } else { //不能排除中位数 |
| 75 | + $right = $middle; |
| 76 | + } |
| 77 | + } |
| 78 | + //相返回left还是right都可以 因为必然存在left==right |
| 79 | + return $left; |
| 80 | + } |
| 81 | +
|
| 82 | +``` |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | +**这个模板第一步就是解除歧义,所有的判断都是left<right,这样最终肯定存在left==right不用再去进一步根据题意取哪边,语句中只剩下两个分支(请忽略掉其他的逻辑来看),一个往左边收,一个往右边收,至于结果,如果不存在其他的题意 随便返回left或者right,有些题目是会变的,那么具体也在最后循环体外自行处理。接着中位数问题,每一次的判断往哪边靠,最大的争议点在于中位数是否要排除,必然会存在满足或者不满足,如果一方排除了中位数,那边另一方必然不能再排除,否则结果必错。这里还有一个地方很重要也值得你去思考的一个点。** |
| 87 | + |
| 88 | +``` |
| 89 | + // $middle=floor(($l+$r)/2); |
| 90 | + // $middle=$l+floor(($r-$l)/2); |
| 91 | + // $middle=$l+(($r-$l)>>1); |
| 92 | + // $middle = ($l + $r) >> 1; |
| 93 | + //上面的结果都是一样的,只是在数值大的情况下值会溢出 |
| 94 | + |
| 95 | +[1,3,6,9,12] 奇数的时候中位数没有争议直接是6 (0+4)/2 位置2=6 |
| 96 | +[1,3,6,9] 如果是($l + $r) >> 1 最终取的是左中位3 也就是位置1 |
| 97 | +[1,3,6,9] 想要右中位那么($l + $r + 1) >> 1 取6 |
| 98 | +
|
| 99 | +``` |
| 100 | + |
| 101 | +**取左中位还是右中位重要吗?很重要!!!!稍不留神,你的程序就是死的.这里有个窍门就是如果你选择的是左中位数,那么在左边界语句缩边的同时一定要把中位数排除,为什么,道理很简单** |
| 102 | + |
| 103 | +``` |
| 104 | + [1,3,6,9] |
| 105 | + 就像现在这样 |
| 106 | + // $middle = ($l + $r) >> 1; 此时中位数是3 |
| 107 | +if(排除中位数的语句){ |
| 108 | + $right = $miidle+1 |
| 109 | +}else{ |
| 110 | + $left=$middle; |
| 111 | +} |
| 112 | + //这时候对于如果走进的是else,就是灾难,因为left并不收缩, |
| 113 | + //这时候就进入了死循 |
| 114 | +
|
| 115 | +``` |
| 116 | + |
| 117 | +**所以你可以看到上面解题,我选择的是左中位数,我的左边界语句可以排除左中位数,就没问题。所以如果中位数选择的是左中位数,并且程序的左边界并不能排除中位数,那么这时候就应该选择右中位数,反之也是同样的道理。为了不吹牛,我拿出一个选右中位数的例子。为了省事直接拿的Leetcode china上的。** |
| 118 | + |
| 119 | + |
| 120 | +<a href="https://github.com/wuqinqiang/"> |
| 121 | + <img src="https://github.com/wuqinqiang/Lettcode-php/blob/master/images/69.png"> |
| 122 | +</a> |
| 123 | + |
| 124 | +**这道题二分查找按照上面规则的解。** |
| 125 | + |
| 126 | +``` |
| 127 | + /** |
| 128 | + * @param Integer $x |
| 129 | + * @return Integer |
| 130 | + */ |
| 131 | + function mySqrt($x) |
| 132 | + { |
| 133 | + $left = 0; |
| 134 | + $right = $x; |
| 135 | + while ($left < $right) { |
| 136 | + $middle = ($left + $right + 1) >> 1; |
| 137 | + // $middle=($left+$right)>>1; |
| 138 | + if ($middle * $middle > $x) { |
| 139 | + $right = $middle - 1; |
| 140 | + } else { |
| 141 | + $left = $middle; |
| 142 | + } |
| 143 | + } |
| 144 | + return $left; |
| 145 | + } |
| 146 | +``` |
| 147 | + |
| 148 | + |
| 149 | +**注意看这里我选择了右中位数。下面我们来解释一下如果选择左中位数会咋么样。道理解释起来也很简单,按照题目的意思,一个数的平方大于目标数,那么它一定不会是目标数的平方根(此时右边界语句一定能排除掉中位数!),反之,如果一个数的平方小于等于目标数,那么平方根可能就是中位数(并不能把左中位数排除),左边界语句不能排除中位数,那么此时程序必要进入死循环,所以这时候我们需要取右中位数。** |
| 150 | + |
| 151 | + |
| 152 | +**所以什么时候知道取哪边呢,也很简单,你可以先随便取左或者右,在收缩边界的时候,第一个语句直接写能排除中位数的,那么另一个边界一定不能排除中位数。然后再去判断取的那边中位数在边界的条件下是否能收缩空间,如若不能,反向获取中位数。当然这里的二分场景并不是很复杂,到了更复杂的二分场景,可能还需要做更多的操作。** |
| 153 | + |
| 154 | +#### 最后再来一道 |
| 155 | + |
| 156 | +<a href="https://github.com/wuqinqiang/"> |
| 157 | + <img src="https://github.com/wuqinqiang/Lettcode-php/blob/master/images/74.png"> |
| 158 | +</a> |
| 159 | +**这道题稍微加了一点游戏难度,当然我可没说用暴力破解法,也不说解题思路了,直接贴按照上面说的模板解题代码** |
| 160 | +``` |
| 161 | + /** |
| 162 | + * @param Integer[][] $matrix |
| 163 | + * @param Integer $target |
| 164 | + * @return Boolean |
| 165 | + */ |
| 166 | + function searchMatrix($matrix, $target) |
| 167 | + { |
| 168 | + $m = count($matrix); |
| 169 | + if ($m == 0) return false; |
| 170 | + $n = count($matrix[0]); |
| 171 | + if ($n == 0) return false; |
| 172 | + $left = 0; |
| 173 | + $right = $m * $n - 1; |
| 174 | + while ($left < $right) { |
| 175 | + $middle = ($left + $right) >> 1; |
| 176 | + $res = $matrix[$middle / $n][$middle % $n]; |
| 177 | + if ($res < $target) { |
| 178 | + $left = $middle + 1; |
| 179 | + } else { |
| 180 | + $right = $middle; |
| 181 | + } |
| 182 | +
|
| 183 | + } |
| 184 | + return $matrix[$left / $n][$left % $n] == $target ? true : false; |
| 185 | + } |
| 186 | +
|
| 187 | +``` |
| 188 | +**最后附上这个二分查找思路的地址值得一看:**[十分好用的二分查找模板](https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/) |
0 commit comments