Coding Challenge #11: Non-decreasing arrays

Coding Challenge #11

Hello! Welcome back to another challenge. I am still looking for people to help me out and make challenges as well! So if you’re interested, help me out!

This one’s harder than usual!
Thanks for the epic, one and only, @LegitimatlyMe, for the challenge idea.

As usual, same requirements:

  • Has to be written in Lua 5.1 (the version Roblox uses)
  • The program has to be compatible with Roblox Studio, meaning you can’t use features vanilla Lua has but Luau has disabled
  • You can’t use httpservice to cheat the challenge

A non-decreasing array is an array where any element at index i is less or equal to the element at index i+1 .
{4, 5, 2} is decreasing, because we decreased from 5 to 2. {4, 5, 8} on the other hand is non-decreasing, because each element is less than the element after it.

Given an array A of length n , write a function CanBeNonDecreasing that determines if you can make A non-decreasing by changing at most one element.

Like in the example above, we can make {4, 5, 2} by just changing the 2, so the function returns true for that. {2, 6, 4, 3} would return false, because you can’t make it non-decreasing by just changing one element.
If the function received an already non-decreasing array (like {0, 1, 5, 9} or {5, 6, 6}) return true straight away.

To check your code, get this link, copy the code, and pass your function to the check function, if it passes through all tests, you succeeded! link :link:

Goodluck! You can find a list of all the challenges here or a github version here.

Answer!

First, there a couple of general things that you should notice:

  1. If there is more than a single decreasing number, you can’t make the array non-decreasing (e.g. {1, 2, 1, 0} can’t become non-decreasing)
  2. Changing the decreasing number to the previous number would make the array non-decreasing (assuming the array has a singe decreasing number) (e.g. if you had an array {4, 5, 2} you can make it non-decreasing just by doing {4, 5, 5}

Now, knowing these two things, it might seem obvious that your code would just look like something simple similar to

local function CanBeNonDecreasing(t)
    local flaws = {}
    for i = 1, #t-1 do 
        if t[i] > t[i+1] then --if current index is greater than the next
            table.insert(flaws, i)
        end
    end 
    if #flaws > 1 then return false end --if there is more than a single decreasing number
    return true --else return true 
end

This would cover most simple test cases, but it won’t work for some. Something like {1, 1, 0, 0} would return true because we see that there is only 1 flaw, and that is second index 1 is bigger than third index 0. But when you change 0 to 1 (attempting to make array non-decreasing) you get {1, 1, 1, 0} which is still non-decreasing, so in reality it’s supposed to return true.

The way I wrote my code is, first checking if the array was non-decreasing, if so just return true straight away, if there was more than a single flaw obviously you can’t make it non-decreasing, so return false. Now, if there was a single flaw, what I do is, make two separate attempts to make the array non-decreasing, first attempt is by changing the decreasing number to the previous greater number, or vice versa, changing the previous great number to the decreasing number. Then I check if either one of the attempts was non-decreasing. Technically if the array was still non-decreasing after these attempts, which are the only possible attempts, it can’t become non-decreasing, and I return false, if either of them made it non-decreasing, I return true.


local function CanBeNonDecreasing(t)
  if #t == 0 or #t == 1 then return true end
  local function f(t_, index, change)
    local flaws = {}
    local t = {}
    for i, v in pairs(t_) do
      t[i] = v
    end
    t[index] = change
    for i = 1, #t-1 do 
      if t[i] > t[i+1] then
        table.insert(flaws, i)
      end
    end
    if #flaws == 0 then return true end
  
    return false, flaws[1]
  end

  local index = select(2, f(t,0,0))
  if not(index) then
    return true
  end
 
  if f(t,index,t[index+1]) or f(t,index+1,t[index]) then
    return true
  end

  return false
end

Now my solution isn’t that epic. Your boy @LegitimatlyMe has this even epicer solution that’s too amazing for me to explain.

function getIdxOfLastElementOfNonDecreasingSubsequence(t, i, j)
  for k = i, j - 1 do
    if t[k] > t[k + 1] then
      return k
    end
  end

  return -1
end

local function test(t)
  local n = getIdxOfLastElementOfNonDecreasingSubsequence(t, 1, #t)
  if n == -1 or #t <= 2 then
    return true
  end

  return getIdxOfLastElementOfNonDecreasingSubsequence(t, n + 1, #t) == -1
    and ((t[n - 1] or -math.huge) <= (t[n + 1] or math.huge) 
      or (t[n] or -math.huge) <= (t[n + 2] or math.huge))
end
13 Likes

I’m pretty sure it would be,

Summary
local myBool
for i, _ in ipairs(myArray) do
    if myArray[i+1] then
        if myArray[i] > myArray[i+1] then
            if myBool then
                return false
            else
                myBool = true
            end
        end
    end
end

return true

I know nested ifs are a little wonky but I’m sort of in a time crunch

Edit: D:

1 Like

Ha! Well as I stated in the solution part, this is a disillusion, it won’t work for all cases. Check out the link that I recently put on the topic, it contains a test that sees if your function passed all cases.

1 Like

Yep I just read it and I’m facepalming lol

I’m a bit busy to think about this too much, I’ll edit my answer in a bit, thanks for a tricky problem, these have been fun

1 Like

local function canBeDecreasing(t)
	local t1 = t
	table.sort(t1)
	for i, value in ipairs(t1) do
		if value == t[i] then return true end
	end 
	
	local occurence, ti = nil, os.clock()
	setmetatable(t, {__index = table})
	--[[ local s = t:concat("")
	    for c in s:gmatch"."do if#s-#s:gsub(c,"")<2 then return false end -- The golfed non-repeating character function is from coding challenge #4, by sircfenner since I didn't want to write one right now. 
       return true end
      ]]
	repeat
		local max = math.max(unpack(t))
		local ind = t:find(max)
		if t[ind-1] or t[ind] - 1 > max then occurence += 1
			t:remove(ind)
		end
		wait()
		if occurence then  return false end
	until # t == 0  or os.clock() - ti < 0.3 * #t
	return true
end

If there’s a point this fails at, let me know.

It causes a temporary “freeze” when passing it to the check function linked in the post.

Edit: Yeah works for all cases finally.

1 Like

It fails at {0, 1}. Two-length arrays can always be non-decreasing by the way, so you can simply make a check for that.

If you add that, it fails at {0, 0, 1}, which is where the hard cases start!

1 Like

My solution ended up being pretty much uglytest:

code

local function detect(array)
	for i, curr in ipairs(array) do
		local next = array[i + 1]
		if next and curr > next then
			return false
		end
	end
	
	return true
end

local function CanBeNonDecreasing(array)
	if detect(array) then return true end

	for i, curr in ipairs(array) do
		table.remove(array, i)
		if detect(array) then
			return true
		else
			table.insert(array, i, curr)
		end
	end
	
	return false
end

Oops. Probably related to the fact I misunderstood the problem and my debugging led me to realize a few of the edge cases I had to account for.

4 Likes

Heya fellas, my solution might seem like black magic, but I assure you that its actually quite simple.

The getIdxOfLastElementOfNonDecreasingSubsequence function, like its overly verbose name suggests, finds the index of the last element of a non-decreasing subarray. For the sake of this text post, I’ll shorten it to getLastIdx(t, i, j). t is the array to be inspected while i and j control the bounds.

TRIVIAL CASES

local n = getIdxOfLastElementOfNonDecreasingSubsequence(t, 1, #t)
if n == -1 or #t <= 2 then
  return true
end
  1. {}
    • The lack of elements means that it can’t decrease, thus this array can always be made non-decreasing.
  2. {a}
    • Since there’s only one element, the condition T[i] <= T[i + 1] holds for every valid value of i, since there is no valid value of i.
  3. {a, b}
    • Change T[2] = a. This works for every array with two elements.
  4. getLastIdx(arr, 1, #arr) == -1
    • This array is already non-decreasing! Note that the challenge says “change at most one element”, thus the option to change none is perfectly valid.

NON-TRIVIAL CASES

return getIdxOfLastElementOfNonDecreasingSubsequence(t, n + 1, #t) == -1
  and ((t[n - 1] or -math.huge) <= (t[n + 1] or math.huge) 
    or (t[n] or -math.huge) <= (t[n + 2] or math.huge))
  1. getLastIdx(t, n + 1, #t) == -1

    • This condition checks for if the rest of the array is a non-decreasing subarray. If it isn’t, then we can’t change just one element since there are two positions in the array which violate the T[i] <= T[i + 1] condition!
    • If this condition passes, the code has, in essence, split the array into two non-decreasing, disjoint arrays. The two following conditions simply check if they can be merged together. After the split, there’s only four elements which should be looked at (other elements of the array play no part in determining if these two arrays can be joined).
  2. (t[n - 1] or -math.huge) <= (t[n + 1] or math.huge)

    • This covers the cases in where t[n] is just a rather large jump in value. Since we know that the indices 1 .. n are non decreasing, we know that t[n - 1] <= t[n] since if this was false, then n should be a lower index; we also know that t[n] > t[n + 1] since if this was false, then n would not be the last element of the non-decreasing subarray.

      Taking these two facts, if we know that T[n - 1] <= T[n + 1] < T[n] and that the indices n + 1 .. #t is non-decreasing (refer to 1), we can change the value of T[n] = T[n + 1] or T[n] = T[n - 1] and T[n - 1] <= T[n] <= T[n + 1] would successfully hold and thus result in a non-decreasing array.

  3. (t[n] or -math.huge) <= (t[n + 2] or math.huge)

    • This covers the case in where t[n + 1] is a very sudden drop in value. Again, we know that the indices 1 .. n are non-decreasing, and the indices n + 1 .. #t are non-decreasing, this condition simply checks if they can be joined together properly. We know that t[n + 2] >= t[n + 1] and that t[n] > t[n + 1]

      Taking these two facts, if we know that t[n] <= t[n + 2], then we know that t[n + 1] < t[n] <= t[n + 2] so we can just change t[n + 1] = t[n] or t[n + 1] = t[n + 2] and t[n] <= t[n + 1] <= t[n + 2] would hold.

TL;DR
The function checks if the array can be partitioned into two non-decreasing subarrays, and checks the boundaries of said subarrays to determine if they can be joined together by changing one of the edge elements.

4 Likes

Also, for extra clarification, -math.huge and math.huge are used since they’re the minimal and maximal elements. ... or -math.huge and ... or math.huge check if ... exists, and if not, replace with the minimal and maximal elements.

These two numbers hold the property that -math.huge <= x and x <= math.huge for all values of x, meaning that if values in an array don’t exist (take n = 1 or n = #t - 1), they wouldn’t affect the conditions.

t[n] or -math.huge in the second condition is completely unnecessary since we know that 1 <= n < #t, but it’s there for the sake of symmetry.

3 Likes

I know I’m late but hey Great Challenge, managed to solve it.

function CanBeNonDecreasing (Table):boolean
	local Passed = {}
	local NotPassedLast,NotPassedAfter
	local TotalFalse = 0
	for index = 1,#Table do
		local Last = Table[index]
		local After = Table[index + 1]
		if After ~= nil then
			if Last <= After then
				table.insert(Passed,Last)
			else
				NotPassedLast,NotPassedAfter = Last,After
				TotalFalse+=1
			end
		end
	end
	if TotalFalse > 1 then
		return false
	elseif TotalFalse == 0 then
		return true
	else
		for index = 1,#Table do
			if Table[index] == NotPassedAfter then
				return true
			end
		end
	end
end